diff --git a/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java b/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java index 1c21eb749c8..202979e5d9f 100644 --- a/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -1833,13 +1833,27 @@ private void queueMoveOrCopy(Account account, String srcFolder, String destFolde queuePendingCommand(account, command); } - private void queueMoveOrCopy(Account account, String srcFolder, String destFolder, + private void queueMoveOrCopy(Account srcAccount, String srcFolder, Account destAccount, String destFolder, boolean isCopy, List uids, Map uidMap) { - if (uidMap == null || uidMap.isEmpty()) { - queueMoveOrCopy(account, srcFolder, destFolder, isCopy, uids); + if (srcAccount.equals(destAccount)) { + if (uidMap == null || uidMap.isEmpty()) { + queueMoveOrCopy(srcAccount, srcFolder, destFolder, isCopy, uids); + } else { + PendingCommand command = PendingMoveOrCopy.create(srcFolder, destFolder, isCopy, uidMap); + queuePendingCommand(srcAccount, command); + } } else { - PendingCommand command = PendingMoveOrCopy.create(srcFolder, destFolder, isCopy, uidMap); - queuePendingCommand(account, command); + if (uidMap == null) { + return; + } + + for (String uid : uidMap.values()) { + PendingCommand command = PendingAppend.create(destFolder, uid); + queuePendingCommand(destAccount, command); + } + if (!isCopy) { + queueSetFlag(srcAccount, srcFolder, true, Flag.DELETED, uids); + } } } @@ -2875,7 +2889,7 @@ public boolean isCopyCapable(final Account account) { } public void moveMessages(final Account srcAccount, final String srcFolder, - List messageReferences, final String destFolder) { + List messageReferences, final Account destAccount, final String destFolder) { actOnMessageGroup(srcAccount, srcFolder, messageReferences, new MessageActor() { @Override public void act(final Account account, LocalFolder messageFolder, final List messages) { @@ -2884,7 +2898,7 @@ public void act(final Account account, LocalFolder messageFolder, final List messageReferences, final String destFolder) { + final List messageReferences, final Account destAccount, final String destFolder) { actOnMessageGroup(srcAccount, srcFolder, messageReferences, new MessageActor() { @Override public void act(final Account account, LocalFolder messageFolder, final List messages) { @@ -2903,7 +2917,8 @@ public void act(final Account account, LocalFolder messageFolder, final List messagesInThreads = collectMessagesInThreads(account, messages); - moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destFolder, false); + moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destAccount, destFolder, + false); } catch (MessagingException e) { Timber.e(e, "Exception while moving messages"); } @@ -2915,18 +2930,18 @@ public void run() { public void moveMessage(final Account account, final String srcFolder, final MessageReference message, final String destFolder) { - moveMessages(account, srcFolder, Collections.singletonList(message), destFolder); + moveMessages(account, srcFolder, Collections.singletonList(message), account, destFolder); } public void copyMessages(final Account srcAccount, final String srcFolder, - final List messageReferences, final String destFolder) { + final List messageReferences, final Account destAccount, final String destFolder) { actOnMessageGroup(srcAccount, srcFolder, messageReferences, new MessageActor() { @Override public void act(final Account account, LocalFolder messageFolder, final List messages) { putBackground("copyMessages", null, new Runnable() { @Override public void run() { - moveOrCopyMessageSynchronous(srcAccount, srcFolder, messages, destFolder, true); + moveOrCopyMessageSynchronous(srcAccount, srcFolder, messages, destAccount, destFolder, true); } }); } @@ -2934,7 +2949,7 @@ public void run() { } public void copyMessagesInThread(Account srcAccount, final String srcFolder, - final List messageReferences, final String destFolder) { + final List messageReferences, final Account destAccount, final String destFolder) { actOnMessageGroup(srcAccount, srcFolder, messageReferences, new MessageActor() { @Override public void act(final Account account, LocalFolder messageFolder, final List messages) { @@ -2943,7 +2958,7 @@ public void act(final Account account, LocalFolder messageFolder, final List messagesInThreads = collectMessagesInThreads(account, messages); - moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destFolder, + moveOrCopyMessageSynchronous(account, srcFolder, messagesInThreads, destAccount, destFolder, true); } catch (MessagingException e) { Timber.e(e, "Exception while copying messages"); @@ -2957,24 +2972,33 @@ public void run() { public void copyMessage(final Account account, final String srcFolder, final MessageReference message, final String destFolder) { - copyMessages(account, srcFolder, Collections.singletonList(message), destFolder); + copyMessages(account, srcFolder, Collections.singletonList(message), account, destFolder); } - private void moveOrCopyMessageSynchronous(final Account account, final String srcFolder, - final List inMessages, final String destFolder, final boolean isCopy) { + private void moveOrCopyMessageSynchronous(final Account srcAccount, final String srcFolder, + final List inMessages, final Account destAccount, final String destFolder, + final boolean isCopy) { + Timber.i("moveOrCopyMessageSynchronous: source account = %s, source folder = %s, %d messages, " + + "destination account = %s, destination folder = %s, isCopy = %s", srcAccount.getName(), + srcFolder, inMessages.size(), destAccount.getName(), destFolder, isCopy); try { - LocalStore localStore = account.getLocalStore(); - RemoteStore remoteStore = account.getRemoteStore(); - if (!isCopy && !remoteStore.isMoveCapable()) { + LocalStore srcLocalStore = srcAccount.getLocalStore(); + RemoteStore srcRemoteStore = srcAccount.getRemoteStore(); + LocalStore destLocalStore = destAccount.getLocalStore(); + RemoteStore destRemoteStore = destAccount.getRemoteStore(); + + if (!isCopy && (!srcRemoteStore.isMoveCapable() || !destRemoteStore.isMoveCapable())) { + Timber.e("Cannot move: incompatible source or destination store!"); return; } - if (isCopy && !remoteStore.isCopyCapable()) { + if (isCopy && (!srcRemoteStore.isCopyCapable() || !destRemoteStore.isCopyCapable())) { + Timber.e("Cannot copy: incompatible source or destination store!"); return; } - LocalFolder localSrcFolder = localStore.getFolder(srcFolder); - Folder localDestFolder = localStore.getFolder(destFolder); + LocalFolder localSrcFolder = srcLocalStore.getFolder(srcFolder); + LocalFolder localDestFolder = destLocalStore.getFolder(destFolder); boolean unreadCountAffected = false; List uids = new LinkedList<>(); @@ -2997,9 +3021,6 @@ private void moveOrCopyMessageSynchronous(final Account account, final String sr origUidMap.put(message.getUid(), message); } - Timber.i("moveOrCopyMessageSynchronous: source folder = %s, %d messages, destination folder = %s, " + - "isCopy = %s", srcFolder, messages.size(), destFolder, isCopy); - Map uidMap; if (isCopy) { @@ -3014,7 +3035,7 @@ private void moveOrCopyMessageSynchronous(final Account account, final String sr // folder, notify the listeners. int unreadMessageCount = localDestFolder.getUnreadMessageCount(); for (MessagingListener l : getListeners()) { - l.folderStatusChanged(account, destFolder, unreadMessageCount); + l.folderStatusChanged(srcAccount, destFolder, unreadMessageCount); } } } else { @@ -3023,10 +3044,10 @@ private void moveOrCopyMessageSynchronous(final Account account, final String sr String origUid = entry.getKey(); Message message = entry.getValue(); for (MessagingListener l : getListeners()) { - l.messageUidChanged(account, srcFolder, origUid, message.getUid()); + l.messageUidChanged(srcAccount, srcFolder, origUid, message.getUid()); } } - unsuppressMessages(account, messages); + unsuppressMessages(srcAccount, messages); if (unreadCountAffected) { // If this move operation changes the unread count, notify the listeners @@ -3034,17 +3055,18 @@ private void moveOrCopyMessageSynchronous(final Account account, final String sr int unreadMessageCountSrc = localSrcFolder.getUnreadMessageCount(); int unreadMessageCountDest = localDestFolder.getUnreadMessageCount(); for (MessagingListener l : getListeners()) { - l.folderStatusChanged(account, srcFolder, unreadMessageCountSrc); - l.folderStatusChanged(account, destFolder, unreadMessageCountDest); + l.folderStatusChanged(srcAccount, srcFolder, unreadMessageCountSrc); + l.folderStatusChanged(srcAccount, destFolder, unreadMessageCountDest); } } } List origUidKeys = new ArrayList<>(origUidMap.keySet()); - queueMoveOrCopy(account, srcFolder, destFolder, isCopy, origUidKeys, uidMap); + queueMoveOrCopy(srcAccount, srcFolder, destAccount, destFolder, isCopy, origUidKeys, uidMap); } - processPendingCommands(account); + processPendingCommands(srcAccount); + processPendingCommandsSynchronous(destAccount); } catch (UnavailableStorageException e) { Timber.i("Failed to move/copy message because storage is not available - trying again later."); throw new UnavailableAccountException(e); @@ -3255,8 +3277,7 @@ private void deleteMessagesSynchronous(final Account account, final String folde if (folder.equals(account.getTrashFolderName())) { queueSetFlag(account, folder, true, Flag.DELETED, syncedMessageUids); } else { - queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, - syncedMessageUids, uidMap); + queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, syncedMessageUids); } processPendingCommands(account); } else if (account.getDeletePolicy() == DeletePolicy.MARK_AS_READ) { @@ -3264,6 +3285,8 @@ private void deleteMessagesSynchronous(final Account account, final String folde processPendingCommands(account); } else { Timber.d("Delete policy %s prevents delete from server", account.getDeletePolicy()); + // TODO + // queueMoveOrCopy(account, folder, account, account.getTrashFolderName(), false, uids, uidMap); } } diff --git a/k9mail/src/main/java/com/fsck/k9/fragment/MessageListFragment.java b/k9mail/src/main/java/com/fsck/k9/fragment/MessageListFragment.java index 749db4c089c..1213634adeb 100644 --- a/k9mail/src/main/java/com/fsck/k9/fragment/MessageListFragment.java +++ b/k9mail/src/main/java/com/fsck/k9/fragment/MessageListFragment.java @@ -31,7 +31,6 @@ import android.support.v4.content.LocalBroadcastManager; import android.support.v4.widget.SwipeRefreshLayout; import android.text.TextUtils; -import timber.log.Timber; import android.view.ActionMode; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -57,6 +56,7 @@ import com.fsck.k9.Preferences; import com.fsck.k9.R; import com.fsck.k9.activity.ActivityListener; +import com.fsck.k9.activity.ChooseAccount; import com.fsck.k9.activity.ChooseFolder; import com.fsck.k9.activity.FolderInfoHolder; import com.fsck.k9.activity.MessageReference; @@ -83,6 +83,7 @@ import com.fsck.k9.mail.Message; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mailstore.LocalFolder; +import com.fsck.k9.mailstore.LocalMessage; import com.fsck.k9.preferences.StorageEditor; import com.fsck.k9.provider.EmailProvider; import com.fsck.k9.provider.EmailProvider.MessageColumns; @@ -93,6 +94,7 @@ import com.fsck.k9.search.SearchSpecification.SearchCondition; import com.fsck.k9.search.SearchSpecification.SearchField; import com.fsck.k9.search.SqlQueryBuilder; +import timber.log.Timber; import static com.fsck.k9.fragment.MLFProjectionInfo.ACCOUNT_UUID_COLUMN; import static com.fsck.k9.fragment.MLFProjectionInfo.FLAGGED_COLUMN; @@ -123,6 +125,8 @@ public static MessageListFragment newInstance( private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1; private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2; + private static final int ACTIVITY_CHOOSE_ACCOUNT_MOVE = 3; + private static final int ACTIVITY_CHOOSE_ACCOUNT_COPY = 4; private static final String ARG_SEARCH = "searchObject"; private static final String ARG_THREADED_LIST = "showingThreadedList"; @@ -929,16 +933,49 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { } switch (requestCode) { + case ACTIVITY_CHOOSE_ACCOUNT_MOVE: + case ACTIVITY_CHOOSE_ACCOUNT_COPY: { + if (data == null) { + return; + } + + final String destAccountUuid = data.getStringExtra(ChooseAccount.EXTRA_ACCOUNT_UUID); + + if (destAccountUuid != null) { + + if (!account.getUuid().equals(destAccountUuid) && !checkMessagesDownloaded(activeMessages)) { + Toast.makeText(getActivity(), R.string.move_copy_message_not_fully_downloaded, + Toast.LENGTH_LONG).show(); + return; + } + + String folderName; + if (isThreadDisplay) { + folderName = activeMessages.get(0).getFolderName(); + } else if (singleFolderMode) { + folderName = currentFolder.folder.getName(); + } else { + folderName = null; + } + + int chooseFolderRequestCode = requestCode == ACTIVITY_CHOOSE_ACCOUNT_MOVE ? ACTIVITY_CHOOSE_FOLDER_MOVE + : ACTIVITY_CHOOSE_FOLDER_COPY; + + displayFolderChoice(chooseFolderRequestCode, folderName, destAccountUuid, null, activeMessages); + } + break; + } case ACTIVITY_CHOOSE_FOLDER_MOVE: case ACTIVITY_CHOOSE_FOLDER_COPY: { if (data == null) { return; } + final String destAccountName = data.getStringExtra(ChooseFolder.EXTRA_ACCOUNT); final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER); final List messages = activeMessages; - if (destFolderName != null) { + if (destAccountName != null && destFolderName != null) { activeMessages = null; // don't need it any more @@ -948,11 +985,11 @@ public void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case ACTIVITY_CHOOSE_FOLDER_MOVE: - move(messages, destFolderName); + move(messages, destAccountName, destFolderName); break; case ACTIVITY_CHOOSE_FOLDER_COPY: - copy(messages, destFolderName); + copy(messages, destAccountName, destFolderName); break; } } @@ -1703,19 +1740,8 @@ private void onMove(List messages) { return; } - String folderName; - if (isThreadDisplay) { - folderName = messages.get(0).getFolderName(); - } else if (singleFolderMode) { - folderName = currentFolder.folder.getName(); - } else { - folderName = null; - } - - - displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folderName, - messages.get(0).getAccountUuid(), null, - messages); + activeMessages = messages; + displayAccountChoice(ACTIVITY_CHOOSE_ACCOUNT_MOVE); } private void onCopy(MessageReference message) { @@ -1733,25 +1759,19 @@ private void onCopy(List messages) { return; } - String folderName; - if (isThreadDisplay) { - folderName = messages.get(0).getFolderName(); - } else if (singleFolderMode) { - folderName = currentFolder.folder.getName(); - } else { - folderName = null; - } - - displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folderName, - messages.get(0).getAccountUuid(), - null, - messages); + activeMessages = messages; + displayAccountChoice(ACTIVITY_CHOOSE_ACCOUNT_COPY); } private void onDebugClearLocally(MessageReference message) { messagingController.debugClearMessagesLocally(Collections.singletonList(message)); } + private void displayAccountChoice(int requestCode) { + Intent intent = new Intent(getActivity(), ChooseAccount.class); + startActivityForResult(intent, requestCode); + } + /** * Helper method to manage the invocation of {@link #startActivityForResult(Intent, int)} for a * folder operation ({@link ChooseFolder} activity), while saving a list of associated messages. @@ -1763,13 +1783,13 @@ private void onDebugClearLocally(MessageReference message) { * @see #startActivityForResult(Intent, int) */ private void displayFolderChoice(int requestCode, String sourceFolderName, - String accountUuid, String lastSelectedFolderName, + String destAccountUuid, String lastSelectedFolderName, List messages) { Intent intent = new Intent(getActivity(), ChooseFolder.class); - intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, accountUuid); + intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, destAccountUuid); intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, lastSelectedFolderName); - if (sourceFolderName == null) { + if (sourceFolderName == null || !account.getUuid().equals(destAccountUuid)) { intent.putExtra(ChooseFolder.EXTRA_SHOW_CURRENT, "yes"); } else { intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, sourceFolderName); @@ -1792,7 +1812,7 @@ private void onArchive(final List messages) { String archiveFolder = account.getArchiveFolderName(); if (!K9.FOLDER_NONE.equals(archiveFolder)) { - move(entry.getValue(), archiveFolder); + move(entry.getValue(), messages.get(0).getAccountUuid(), archiveFolder); } } } @@ -1841,7 +1861,7 @@ private void onSpamConfirmed(List messages) { String spamFolder = account.getSpamFolderName(); if (!K9.FOLDER_NONE.equals(spamFolder)) { - move(entry.getValue(), spamFolder); + move(entry.getValue(), messages.get(0).getAccountUuid(), spamFolder); } } } @@ -1889,43 +1909,72 @@ private boolean checkCopyOrMovePossible(final List messages, return true; } + private boolean checkMessagesDownloaded(List messages) { + LocalFolder localFolder = null; + for (MessageReference message : messages) { + try { + Account account = preferences.getAccount(message.getAccountUuid()); + localFolder = account.getLocalStore().getFolder(message.getFolderName()); + localFolder.open(Folder.OPEN_MODE_RO); + LocalMessage localMessage = localFolder.getMessage(message.getUid()); + if (!localMessage.isSet(Flag.X_DOWNLOADED_FULL)) { + return false; + } + } catch (MessagingException me) { + Timber.e(me, "Could not check if message was fully downloaded"); + return false; + } finally { + if (localFolder != null) { + localFolder.close(); + } + } + } + return true; + } + /** * Copy the specified messages to the specified folder. * * @param messages * List of messages to copy. Never {@code null}. - * @param destination + * @param destAccount + * The uuid of the destination account. Never {@code null}. + * @param destFolder * The name of the destination folder. Never {@code null}. */ - private void copy(List messages, final String destination) { - copyOrMove(messages, destination, FolderOperation.COPY); + private void copy(List messages, final String destAccount, final String destFolder) { + copyOrMove(messages, destAccount, destFolder, FolderOperation.COPY); } /** * Move the specified messages to the specified folder. * * @param messages - * The list of messages to move. Never {@code null}. - * @param destination + * List of messages to copy. Never {@code null}. + * @param destAccount + * The uuid of the destination account. Never {@code null}. + * @param destFolder * The name of the destination folder. Never {@code null}. */ - private void move(List messages, final String destination) { - copyOrMove(messages, destination, FolderOperation.MOVE); + private void move(List messages, final String destAccount, final String destFolder) { + copyOrMove(messages, destAccount, destFolder, FolderOperation.MOVE); } /** - * The underlying implementation for {@link #copy(List, String)} and - * {@link #move(List, String)}. This method was added mainly because those 2 + * The underlying implementation for {@link #copy(List, String, String)} and + * {@link #move(List, String, String)}. This method was added mainly because those 2 * methods share common behavior. * * @param messages * The list of messages to copy or move. Never {@code null}. - * @param destination + * @param destAccountUuid + * The uuid of the destination account. Never {@code null}. + * @param destFolderName * The name of the destination folder. Never {@code null} or {@link K9#FOLDER_NONE}. * @param operation * Specifies what operation to perform. Never {@code null}. */ - private void copyOrMove(List messages, final String destination, + private void copyOrMove(List messages, final String destAccountUuid, final String destFolderName, final FolderOperation operation) { Map> folderMap = new HashMap<>(); @@ -1943,8 +1992,9 @@ private void copyOrMove(List messages, final String destinatio return; } + String accountUuid = message.getAccountUuid(); String folderName = message.getFolderName(); - if (folderName.equals(destination)) { + if (accountUuid.equals(destAccountUuid) && folderName.equals(destFolderName)) { // Skip messages already in the destination folder continue; } @@ -1961,19 +2011,22 @@ private void copyOrMove(List messages, final String destinatio for (Map.Entry> entry : folderMap.entrySet()) { String folderName = entry.getKey(); List outMessages = entry.getValue(); - Account account = preferences.getAccount(outMessages.get(0).getAccountUuid()); + Account srcAccount = preferences.getAccount(outMessages.get(0).getAccountUuid()); + Account destAccount = preferences.getAccount(destAccountUuid); if (operation == FolderOperation.MOVE) { if (showingThreadedList) { - messagingController.moveMessagesInThread(account, folderName, outMessages, destination); + messagingController.moveMessagesInThread(srcAccount, folderName, outMessages, destAccount, + destFolderName); } else { - messagingController.moveMessages(account, folderName, outMessages, destination); + messagingController.moveMessages(srcAccount, folderName, outMessages, destAccount, destFolderName); } } else { if (showingThreadedList) { - messagingController.copyMessagesInThread(account, folderName, outMessages, destination); + messagingController.copyMessagesInThread(srcAccount, folderName, outMessages, destAccount, + destFolderName); } else { - messagingController.copyMessages(account, folderName, outMessages, destination); + messagingController.copyMessages(srcAccount, folderName, outMessages, destAccount, destFolderName); } } } diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalFolder.java b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalFolder.java index 63d863ba00a..20ba6ea107f 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalFolder.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalFolder.java @@ -37,6 +37,7 @@ import com.fsck.k9.mail.BodyPart; import com.fsck.k9.mail.BoundaryGenerator; import com.fsck.k9.mail.FetchProfile; +import com.fsck.k9.mail.FetchProfile.Item; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Message; @@ -1070,6 +1071,18 @@ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, Unavailab lMessage.getDatabaseId(), getName()); + if (!getAccount().equals(((LocalFolder) destFolder).getAccount())) { + FetchProfile fp = new FetchProfile(); + fp.add(Item.ENVELOPE); + fp.add(Item.BODY); + fetch(Collections.singletonList(lMessage), fp, null); + String newUid = copyMessages(Collections.singletonList(message), destFolder) + .get(oldUID); + message.destroy(); + uidMap.put(oldUID, newUid); + return null; + } + String newUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString(); message.setUid(newUid); diff --git a/k9mail/src/main/res/values/strings.xml b/k9mail/src/main/res/values/strings.xml index e3af5261010..282365d2ab4 100644 --- a/k9mail/src/main/res/values/strings.xml +++ b/k9mail/src/main/res/values/strings.xml @@ -520,6 +520,8 @@ Please submit bug reports, contribute new features and ask questions at all messages Cannot copy or move a message that is not synchronized with the server + Cannot copy or move a message across accounts that is not + fully downloaded Setup could not finish Username or password incorrect.\n(%s)