1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-11 12:05:06 -05:00

Merge branch 'uidplus'

This commit is contained in:
cketti 2012-02-29 14:09:41 +01:00
commit 5591865f17
8 changed files with 573 additions and 60 deletions

View File

@ -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,14 +2167,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 = (LocalFolder) localStore.getFolder(destFolder);
List<Message> messages = new ArrayList<Message>();
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<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));
}
}
}
@ -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) {

View File

@ -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) {

View File

@ -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",
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);
//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);
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) {
message.setUid(newUid);
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);
}

View File

@ -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();
}

View File

@ -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

View File

@ -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 {

View 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;
}
}

View 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());
}
}