mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-27 11:42:16 -05:00
Merge branch 'uidplus'
This commit is contained in:
commit
5591865f17
@ -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";
|
||||
@ -1900,6 +1901,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);
|
||||
@ -2079,16 +2082,72 @@ 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];
|
||||
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<String, String> 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_NEW;
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert pending command to new format and call
|
||||
* {@link #processPendingMoveOrCopy(PendingCommand, Account)}.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
@ -2100,6 +2159,7 @@ public class MessagingController implements Runnable {
|
||||
throws MessagingException {
|
||||
Folder remoteSrcFolder = null;
|
||||
Folder remoteDestFolder = null;
|
||||
LocalFolder localDestFolder = null;
|
||||
try {
|
||||
String srcFolder = command.arguments[0];
|
||||
if (account.getErrorFolderName().equals(srcFolder)) {
|
||||
@ -2107,17 +2167,45 @@ 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 = (LocalFolder) localStore.getFolder(destFolder);
|
||||
List<Message> messages = new ArrayList<Message>();
|
||||
for (int i = 3; i < command.arguments.length; i++) {
|
||||
|
||||
/*
|
||||
* We split up the localUidMap into two parts while sending the command, here we assemble it back.
|
||||
*/
|
||||
Map<String, String> localUidMap = new HashMap<String, String>();
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isCopy = false;
|
||||
if (isCopyS != null) {
|
||||
isCopy = Boolean.parseBoolean(isCopyS);
|
||||
@ -2135,6 +2223,8 @@ public class MessagingController implements Runnable {
|
||||
Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy: source folder = " + srcFolder
|
||||
+ ", " + messages.size() + " messages, destination folder = " + destFolder + ", isCopy = " + isCopy);
|
||||
|
||||
Map <String, String> remoteUidMap = null;
|
||||
|
||||
if (!isCopy && destFolder.equals(account.getTrashFolderName())) {
|
||||
if (K9.DEBUG)
|
||||
Log.d(K9.LOG_TAG, "processingPendingMoveOrCopy doing special case for deleting message");
|
||||
@ -2148,9 +2238,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())) {
|
||||
@ -2159,12 +2249,32 @@ 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 != null && !remoteUidMap.isEmpty()) {
|
||||
Set<String> remoteSrcUids = remoteUidMap.keySet();
|
||||
Iterator<String> remoteSrcUidsIterator = remoteSrcUids.iterator();
|
||||
|
||||
while (remoteSrcUidsIterator.hasNext()) {
|
||||
String remoteSrcUid = remoteSrcUidsIterator.next();
|
||||
String localDestUid = localUidMap.get(remoteSrcUid);
|
||||
String newUid = remoteUidMap.get(remoteSrcUid);
|
||||
|
||||
Message localDestMessage = localDestFolder.getMessage(localDestUid);
|
||||
localDestMessage.setUid(newUid);
|
||||
localDestFolder.changeUid((LocalMessage)localDestMessage);
|
||||
for (MessagingListener l : getListeners()) {
|
||||
l.messageUidChanged(account, destFolder, localDestUid, newUid);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
closeFolder(remoteSrcFolder);
|
||||
closeFolder(remoteDestFolder);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void queueSetFlag(final Account account, final String folderName, final String newState, final String flag, final String[] uids) {
|
||||
@ -3281,6 +3391,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<String, String> uidMap = new HashMap<String, String>();
|
||||
Store localStore = account.getLocalStore();
|
||||
Store remoteStore = account.getRemoteStore();
|
||||
if (!isCopy && (!remoteStore.isMoveCapable() || !localStore.isMoveCapable())) {
|
||||
@ -3323,7 +3434,7 @@ 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);
|
||||
|
||||
if (unreadCountAffected) {
|
||||
// If this copy operation changes the unread count in the destination
|
||||
@ -3334,7 +3445,7 @@ public class MessagingController implements Runnable {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
localSrcFolder.moveMessages(messages, localDestFolder);
|
||||
uidMap = localSrcFolder.moveMessages(messages, localDestFolder);
|
||||
for (Map.Entry<String, Message> entry : origUidMap.entrySet()) {
|
||||
String origUid = entry.getKey();
|
||||
Message message = entry.getValue();
|
||||
@ -3356,7 +3467,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);
|
||||
@ -3436,6 +3547,7 @@ public class MessagingController implements Runnable {
|
||||
}
|
||||
Store localStore = account.getLocalStore();
|
||||
localFolder = localStore.getFolder(folder);
|
||||
Map<String, String> 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");
|
||||
@ -3450,7 +3562,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);
|
||||
|
||||
}
|
||||
}
|
||||
@ -3483,7 +3595,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) {
|
||||
|
@ -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<String, String> appendMessages(Message[] messages) throws MessagingException;
|
||||
|
||||
public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {}
|
||||
public Map<String, String> copyMessages(Message[] msgs, Folder folder) throws MessagingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void moveMessages(Message[] msgs, Folder folder) throws MessagingException {}
|
||||
public Map<String, String> moveMessages(Message[] msgs, Folder folder) throws MessagingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
public void delete(Message[] msgs, String trashFolderName) throws MessagingException {
|
||||
for (Message message : msgs) {
|
||||
|
@ -61,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;
|
||||
@ -89,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;
|
||||
@ -1061,53 +1063,127 @@ public class ImapStore extends Store {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the given messages to the specified folder.
|
||||
*
|
||||
* <p>
|
||||
* <strong>Note:</strong>
|
||||
* Only the UIDs of the given {@link Message} instances are used. It is assumed that all
|
||||
* UIDs represent valid messages in this folder.
|
||||
* </p>
|
||||
*
|
||||
* @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 void copyMessages(Message[] messages, Folder folder) throws MessagingException {
|
||||
public Map<String, String> copyMessages(Message[] messages, Folder folder)
|
||||
throws MessagingException {
|
||||
if (!(folder instanceof ImapFolder)) {
|
||||
throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder");
|
||||
}
|
||||
|
||||
if (messages.length == 0)
|
||||
return;
|
||||
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++) {
|
||||
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);
|
||||
}
|
||||
|
||||
if (exists(remoteDestName)) {
|
||||
executeSimpleCommand(String.format("UID COPY %s %s",
|
||||
//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));
|
||||
} else {
|
||||
throw new MessagingException("IMAPMessage.copyMessages: remote destination folder " + folder.getName()
|
||||
+ " does not exist and could not be created for " + getLogId()
|
||||
, true);
|
||||
remoteDestName), false);
|
||||
ImapResponse response;
|
||||
do {
|
||||
response = mConnection.readResponse();
|
||||
handleUntaggedResponse(response);
|
||||
} while (response.mTag == null);
|
||||
|
||||
Map<String, String> 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<String> srcUids = ImapUtility.getImapSequenceValues(
|
||||
copyList.getString(2));
|
||||
List<String> destUids = ImapUtility.getImapSequenceValues(
|
||||
copyList.getString(3));
|
||||
|
||||
if (srcUids != null && destUids != null) {
|
||||
if (srcUids.size() == destUids.size()) {
|
||||
Iterator<String> srcUidsIterator = srcUids.iterator();
|
||||
Iterator<String> destUidsIterator = destUids.iterator();
|
||||
uidMap = new HashMap<String, String>();
|
||||
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, "Parsing of the sequence set failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return uidMap;
|
||||
} catch (IOException ioe) {
|
||||
throw ioExceptionHandler(mConnection, ioe);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void moveMessages(Message[] messages, Folder folder) throws MessagingException {
|
||||
public Map<String, String> moveMessages(Message[] messages, Folder folder) throws MessagingException {
|
||||
if (messages.length == 0)
|
||||
return;
|
||||
copyMessages(messages, folder);
|
||||
return null;
|
||||
Map<String, String> uidMap = copyMessages(messages, folder);
|
||||
setFlags(messages, new Flag[] { Flag.DELETED }, true);
|
||||
return uidMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1851,20 +1927,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.
|
||||
*
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* @param messages
|
||||
* The messages to append to the folder.
|
||||
*
|
||||
* @return The mapping of original message UIDs to the new server UIDs.
|
||||
*/
|
||||
@Override
|
||||
public void appendMessages(Message[] messages) throws MessagingException {
|
||||
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
|
||||
checkOpen();
|
||||
try {
|
||||
Map<String, String> uidMap = new HashMap<String, String>();
|
||||
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();
|
||||
@ -1878,16 +1964,54 @@ public class ImapStore extends Store {
|
||||
}
|
||||
} while (response.mTag == null);
|
||||
|
||||
String newUid = getUidFromMessageId(message);
|
||||
if (K9.DEBUG)
|
||||
Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId());
|
||||
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 (newUid != null) {
|
||||
if (responseList instanceof ImapList) {
|
||||
ImapList appendList = (ImapList) responseList;
|
||||
if (appendList.size() >= 3 &&
|
||||
appendList.getString(0).equals("APPENDUID")) {
|
||||
|
||||
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.
|
||||
*/
|
||||
String newUid = getUidFromMessageId(message);
|
||||
if (K9.DEBUG) {
|
||||
Log.d(K9.LOG_TAG, "Got UID " + newUid + " for message for " + getLogId());
|
||||
}
|
||||
|
||||
if (!StringUtils.isNullOrEmpty(newUid)) {
|
||||
uidMap.put(message.getUid(), newUid);
|
||||
message.setUid(newUid);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
@ -1928,21 +1928,23 @@ public class LocalStore extends Store implements Serializable {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyMessages(Message[] msgs, Folder folder) throws MessagingException {
|
||||
public Map<String, String> 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<String, String> 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<String, String> uidMap = new HashMap<String, String>();
|
||||
|
||||
try {
|
||||
database.execute(false, new DbCallback<Void>() {
|
||||
@Override
|
||||
@ -1968,7 +1970,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(),
|
||||
@ -1976,6 +1981,11 @@ 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);
|
||||
@ -1987,6 +1997,7 @@ public class LocalStore extends Store implements Serializable {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return uidMap;
|
||||
} catch (WrappedException e) {
|
||||
throw(MessagingException) e.getCause();
|
||||
}
|
||||
@ -2032,8 +2043,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<String, String> appendMessages(Message[] messages) throws MessagingException {
|
||||
return appendMessages(messages, false);
|
||||
}
|
||||
|
||||
public void destroyMessages(final Message[] messages) throws MessagingException {
|
||||
@ -2069,10 +2080,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<String, String> uidMap of srcUids -> destUids
|
||||
*/
|
||||
private void appendMessages(final Message[] messages, final boolean copy) throws MessagingException {
|
||||
private Map<String, String> appendMessages(final Message[] messages, final boolean copy) throws MessagingException {
|
||||
open(OpenMode.READ_WRITE);
|
||||
try {
|
||||
final Map<String, String> uidMap = new HashMap<String, String>();
|
||||
database.execute(true, new DbCallback<Void>() {
|
||||
@Override
|
||||
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
|
||||
@ -2085,11 +2098,26 @@ 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();
|
||||
if (!copy) {
|
||||
message.setUid(uid);
|
||||
}
|
||||
/*
|
||||
* 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) {
|
||||
@ -2169,6 +2197,7 @@ public class LocalStore extends Store implements Serializable {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return uidMap;
|
||||
} catch (WrappedException e) {
|
||||
throw(MessagingException) e.getCause();
|
||||
}
|
||||
|
@ -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 String STORE_TYPE = "POP3";
|
||||
@ -889,7 +890,8 @@ public class Pop3Store extends Store {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendMessages(Message[] messages) throws MessagingException {
|
||||
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1334,13 +1334,15 @@ public class WebDavStore extends Store {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyMessages(Message[] messages, Folder folder) throws MessagingException {
|
||||
public Map<String, String> 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<String, String> moveMessages(Message[] messages, Folder folder) throws MessagingException {
|
||||
moveOrCopyMessages(messages, folder.getName(), true);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1915,8 +1917,9 @@ public class WebDavStore extends Store {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void appendMessages(Message[] messages) throws MessagingException {
|
||||
public Map<String, String> appendMessages(Message[] messages) throws MessagingException {
|
||||
appendWebDavMessages(messages);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Message[] appendWebDavMessages(Message[] messages) throws MessagingException {
|
||||
|
113
src/com/fsck/k9/mail/store/imap/ImapUtility.java
Normal file
113
src/com/fsck/k9/mail/store/imap/ImapUtility.java
Normal file
@ -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.
|
||||
*
|
||||
* <p>
|
||||
* Any ranges are expanded into a list of individual numbers.
|
||||
* </p>
|
||||
*
|
||||
* <pre>
|
||||
* sequence-number = nz-number / "*"
|
||||
* sequence-range = sequence-number ":" sequence-number
|
||||
* sequence-set = (sequence-number / sequence-range) *("," sequence-set)
|
||||
* </pre>
|
||||
*
|
||||
* @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<String> getImapSequenceValues(String set) {
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
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.
|
||||
*
|
||||
* <pre>
|
||||
* sequence-number = nz-number / "*"
|
||||
* sequence-range = sequence-number ":" sequence-number
|
||||
* sequence-set = (sequence-number / sequence-range) *("," sequence-set)
|
||||
* </pre>
|
||||
*
|
||||
* @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<String> getImapRangeValues(String range) {
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
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;
|
||||
}
|
||||
}
|
125
tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java
Normal file
125
tests/src/com/fsck/k9/mail/store/imap/ImapUtilityTest.java
Normal file
@ -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<String> 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<String> 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());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user