Move code to move/copy messages to Backend implementations

This commit is contained in:
cketti 2018-06-17 02:17:36 +02:00
parent 34616a954b
commit af13655de1
8 changed files with 320 additions and 84 deletions

View file

@ -21,4 +21,21 @@ interface Backend {
@Throws(MessagingException::class)
fun expunge(folderServerId: String)
@Throws(MessagingException::class)
fun expungeMessages(folderServerId: String, messageServerIds: List<String>)
@Throws(MessagingException::class)
fun moveMessages(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>?
@Throws(MessagingException::class)
fun copyMessages(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>?
}

View file

@ -1,6 +1,8 @@
package com.fsck.k9.backend.imap;
import java.util.List;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.imap.ImapStore;
@ -35,4 +37,21 @@ class CommandExpunge {
remoteFolder.close();
}
}
void expungeMessages(@NotNull String folderServerId, @NotNull List<String> messageServerIds)
throws MessagingException {
Folder remoteFolder = imapStore.getFolder(folderServerId);
try {
if (!remoteFolder.exists()) {
return;
}
remoteFolder.open(Folder.OPEN_MODE_RW);
if (remoteFolder.getMode() != Folder.OPEN_MODE_RW) {
return;
}
remoteFolder.expungeUids(messageServerIds);
} finally {
remoteFolder.close();
}
}
}

View file

@ -0,0 +1,92 @@
package com.fsck.k9.backend.imap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.fsck.k9.backend.api.BackendFolder;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.imap.ImapFolder;
import com.fsck.k9.mail.store.imap.ImapStore;
import org.jetbrains.annotations.NotNull;
import timber.log.Timber;
class CommandMoveOrCopyMessages {
private final ImapStore imapStore;
CommandMoveOrCopyMessages(ImapStore imapStore) {
this.imapStore = imapStore;
}
Map<String, String> moveMessages(@NotNull String sourceFolderServerId, @NotNull String targetFolderServerId,
@NotNull List<String> messageServerIds) throws MessagingException {
return moveOrCopyMessages(sourceFolderServerId, targetFolderServerId, messageServerIds, false);
}
Map<String, String> copyMessages(@NotNull String sourceFolderServerId, @NotNull String targetFolderServerId,
@NotNull List<String> messageServerIds) throws MessagingException {
return moveOrCopyMessages(sourceFolderServerId, targetFolderServerId, messageServerIds, true);
}
private Map<String, String> moveOrCopyMessages(String srcFolder, String destFolder, Collection<String> uids,
boolean isCopy) throws MessagingException {
ImapFolder remoteSrcFolder = null;
ImapFolder remoteDestFolder = null;
try {
remoteSrcFolder = imapStore.getFolder(srcFolder);
List<Message> messages = new ArrayList<>();
for (String uid : uids) {
// TODO: Local messages should never end up here. Throw in debug builds.
if (!uid.startsWith(BackendFolder.LOCAL_UID_PREFIX)) {
messages.add(remoteSrcFolder.getMessage(uid));
}
}
if (messages.isEmpty()) {
Timber.i("processingPendingMoveOrCopy: no remote messages to move, skipping");
return null;
}
if (!remoteSrcFolder.exists()) {
throw new MessagingException("processingPendingMoveOrCopy: remoteFolder " + srcFolder +
" does not exist", true);
}
remoteSrcFolder.open(Folder.OPEN_MODE_RW);
if (remoteSrcFolder.getMode() != Folder.OPEN_MODE_RW) {
throw new MessagingException("processingPendingMoveOrCopy: could not open remoteSrcFolder " +
srcFolder + " read/write", true);
}
Timber.d("processingPendingMoveOrCopy: source folder = %s, %d messages, " +
"destination folder = %s, isCopy = %s", srcFolder, messages.size(), destFolder, isCopy);
remoteDestFolder = imapStore.getFolder(destFolder);
if (isCopy) {
return remoteSrcFolder.copyMessages(messages, remoteDestFolder);
} else {
return remoteSrcFolder.moveMessages(messages, remoteDestFolder);
}
} finally {
closeFolder(remoteSrcFolder);
closeFolder(remoteDestFolder);
}
}
private static void closeFolder(ImapFolder folder) {
if (folder != null) {
folder.close();
}
}
}

View file

@ -2,6 +2,7 @@ package com.fsck.k9.backend.imap;
import java.util.List;
import java.util.Map;
import com.fsck.k9.backend.api.Backend;
import com.fsck.k9.backend.api.BackendStorage;
@ -12,6 +13,7 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.imap.ImapStore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ImapBackend implements Backend {
@ -19,6 +21,7 @@ public class ImapBackend implements Backend {
private final CommandSetFlag commandSetFlag;
private final CommandMarkAllAsRead commandMarkAllAsRead;
private final CommandExpunge commandExpunge;
private final CommandMoveOrCopyMessages commandMoveOrCopyMessages;
public ImapBackend(String accountName, BackendStorage backendStorage, ImapStore imapStore) {
@ -26,6 +29,7 @@ public class ImapBackend implements Backend {
commandSetFlag = new CommandSetFlag(imapStore);
commandMarkAllAsRead = new CommandMarkAllAsRead(imapStore);
commandExpunge = new CommandExpunge(imapStore);
commandMoveOrCopyMessages = new CommandMoveOrCopyMessages(imapStore);
}
@Override
@ -59,4 +63,24 @@ public class ImapBackend implements Backend {
public void expunge(@NotNull String folderServerId) throws MessagingException {
commandExpunge.expunge(folderServerId);
}
@Override
public void expungeMessages(@NotNull String folderServerId, @NotNull List<String> messageServerIds)
throws MessagingException {
commandExpunge.expungeMessages(folderServerId, messageServerIds);
}
@Nullable
@Override
public Map<String, String> moveMessages(@NotNull String sourceFolderServerId, @NotNull String targetFolderServerId,
@NotNull List<String> messageServerIds) throws MessagingException {
return commandMoveOrCopyMessages.moveMessages(sourceFolderServerId, targetFolderServerId, messageServerIds);
}
@Nullable
@Override
public Map<String, String> copyMessages(@NotNull String sourceFolderServerId, @NotNull String targetFolderServerId,
@NotNull List<String> messageServerIds) throws MessagingException {
return commandMoveOrCopyMessages.copyMessages(sourceFolderServerId, targetFolderServerId, messageServerIds);
}
}

View file

@ -30,4 +30,24 @@ class Pop3Backend(accountName: String, backendStorage: BackendStorage, pop3Store
override fun expunge(folderServerId: String) {
throw UnsupportedOperationException("not supported")
}
override fun expungeMessages(folderServerId: String, messageServerIds: List<String>) {
throw UnsupportedOperationException("not supported")
}
override fun moveMessages(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>? {
throw UnsupportedOperationException("not supported")
}
override fun copyMessages(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>? {
throw UnsupportedOperationException("not supported")
}
}

View file

@ -0,0 +1,92 @@
package com.fsck.k9.backend.webdav;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import com.fsck.k9.backend.api.BackendFolder;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.webdav.WebDavFolder;
import com.fsck.k9.mail.store.webdav.WebDavStore;
import org.jetbrains.annotations.NotNull;
import timber.log.Timber;
class CommandMoveOrCopyMessages {
private final WebDavStore webDavStore;
CommandMoveOrCopyMessages(WebDavStore webDavStore) {
this.webDavStore = webDavStore;
}
Map<String, String> moveMessages(@NotNull String sourceFolderServerId, @NotNull String targetFolderServerId,
@NotNull List<String> messageServerIds) throws MessagingException {
return moveOrCopyMessages(sourceFolderServerId, targetFolderServerId, messageServerIds, false);
}
Map<String, String> copyMessages(@NotNull String sourceFolderServerId, @NotNull String targetFolderServerId,
@NotNull List<String> messageServerIds) throws MessagingException {
return moveOrCopyMessages(sourceFolderServerId, targetFolderServerId, messageServerIds, true);
}
private Map<String, String> moveOrCopyMessages(String srcFolder, String destFolder, Collection<String> uids,
boolean isCopy) throws MessagingException {
WebDavFolder remoteSrcFolder = null;
WebDavFolder remoteDestFolder = null;
try {
remoteSrcFolder = webDavStore.getFolder(srcFolder);
List<Message> messages = new ArrayList<>();
for (String uid : uids) {
// TODO: Local messages should never end up here. Throw in debug builds.
if (!uid.startsWith(BackendFolder.LOCAL_UID_PREFIX)) {
messages.add(remoteSrcFolder.getMessage(uid));
}
}
if (messages.isEmpty()) {
Timber.i("processingPendingMoveOrCopy: no remote messages to move, skipping");
return null;
}
if (!remoteSrcFolder.exists()) {
throw new MessagingException("processingPendingMoveOrCopy: remoteFolder " + srcFolder +
" does not exist", true);
}
remoteSrcFolder.open(Folder.OPEN_MODE_RW);
if (remoteSrcFolder.getMode() != Folder.OPEN_MODE_RW) {
throw new MessagingException("processingPendingMoveOrCopy: could not open remoteSrcFolder " +
srcFolder + " read/write", true);
}
Timber.d("processingPendingMoveOrCopy: source folder = %s, %d messages, " +
"destination folder = %s, isCopy = %s", srcFolder, messages.size(), destFolder, isCopy);
remoteDestFolder = webDavStore.getFolder(destFolder);
if (isCopy) {
return remoteSrcFolder.copyMessages(messages, remoteDestFolder);
} else {
return remoteSrcFolder.moveMessages(messages, remoteDestFolder);
}
} finally {
closeFolder(remoteSrcFolder);
closeFolder(remoteDestFolder);
}
}
private static void closeFolder(WebDavFolder folder) {
if (folder != null) {
folder.close();
}
}
}

View file

@ -13,6 +13,7 @@ class WebDavBackend(accountName: String, backendStorage: BackendStorage, webDavS
private val webDavSync: WebDavSync = WebDavSync(accountName, backendStorage, webDavStore)
private val commandSetFlag = CommandSetFlag(webDavStore)
private val commandMarkAllAsRead = CommandMarkAllAsRead(webDavStore)
private val commandMoveOrCopyMessages = CommandMoveOrCopyMessages(webDavStore)
override val supportsSeenFlag: Boolean = true
override val supportsExpunge: Boolean = true
@ -34,4 +35,24 @@ class WebDavBackend(accountName: String, backendStorage: BackendStorage, webDavS
override fun expunge(folderServerId: String) {
throw UnsupportedOperationException("not supported")
}
override fun expungeMessages(folderServerId: String, messageServerIds: List<String>) {
throw UnsupportedOperationException("not supported")
}
override fun moveMessages(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>? {
return commandMoveOrCopyMessages.moveMessages(sourceFolderServerId, targetFolderServerId, messageServerIds)
}
override fun copyMessages(
sourceFolderServerId: String,
targetFolderServerId: String,
messageServerIds: List<String>
): Map<String, String>? {
return commandMoveOrCopyMessages.copyMessages(sourceFolderServerId, targetFolderServerId, messageServerIds)
}
}

View file

@ -1589,105 +1589,56 @@ public class MessagingController {
boolean isCopy = command.isCopy;
Map<String, String> newUidMap = command.newUidMap;
Collection<String> uids = newUidMap != null ? newUidMap.keySet() : command.uids;
List<String> uids = newUidMap != null ? new ArrayList<>(newUidMap.keySet()) : command.uids;
processPendingMoveOrCopy(account, srcFolder, destFolder, uids, isCopy, newUidMap);
}
@VisibleForTesting
void processPendingMoveOrCopy(Account account, String srcFolder, String destFolder, Collection<String> uids,
void processPendingMoveOrCopy(Account account, String srcFolder, String destFolder, List<String> uids,
boolean isCopy, Map<String, String> newUidMap) throws MessagingException {
Folder remoteSrcFolder = null;
Folder remoteDestFolder = null;
LocalFolder localDestFolder;
try {
RemoteStore remoteStore = account.getRemoteStore();
remoteSrcFolder = remoteStore.getFolder(srcFolder);
LocalStore localStore = account.getLocalStore();
localDestFolder = localStore.getFolder(destFolder);
LocalStore localStore = account.getLocalStore();
localDestFolder = localStore.getFolder(destFolder);
List<Message> messages = new ArrayList<>();
Backend backend = getBackend(account);
for (String uid : uids) {
if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) {
messages.add(remoteSrcFolder.getMessage(uid));
Map<String, String> remoteUidMap;
if (isCopy) {
remoteUidMap = backend.copyMessages(srcFolder, destFolder, uids);
} else {
remoteUidMap = backend.moveMessages(srcFolder, destFolder, uids);
}
if (!isCopy && Expunge.EXPUNGE_IMMEDIATELY == account.getExpungePolicy()) {
Timber.i("processingPendingMoveOrCopy expunging folder %s:%s", account.getDescription(), srcFolder);
backend.expungeMessages(srcFolder, uids);
}
/*
* This next part is used to bring the local UIDs of the local destination folder
* upto speed with the remote UIDs of remote destination folder.
*/
if (newUidMap != null && remoteUidMap != null && !remoteUidMap.isEmpty()) {
Timber.i("processingPendingMoveOrCopy: changing local uids of %d messages", remoteUidMap.size());
for (Entry<String, String> entry : remoteUidMap.entrySet()) {
String remoteSrcUid = entry.getKey();
String newUid = entry.getValue();
String localDestUid = newUidMap.get(remoteSrcUid);
if (localDestUid == null) {
continue;
}
}
if (messages.isEmpty()) {
Timber.i("processingPendingMoveOrCopy: no remote messages to move, skipping");
return;
}
if (!remoteSrcFolder.exists()) {
throw new MessagingException(
"processingPendingMoveOrCopy: remoteFolder " + srcFolder + " does not exist", true);
}
remoteSrcFolder.open(Folder.OPEN_MODE_RW);
if (remoteSrcFolder.getMode() != Folder.OPEN_MODE_RW) {
throw new MessagingException("processingPendingMoveOrCopy: could not open remoteSrcFolder "
+ srcFolder + " read/write", true);
}
Timber.d("processingPendingMoveOrCopy: source folder = %s, %d messages, destination folder = %s, " +
"isCopy = %s", srcFolder, messages.size(), destFolder, isCopy);
Map<String, String> remoteUidMap = null;
if (!isCopy && destFolder.equals(account.getTrashFolder())) {
Timber.d("processingPendingMoveOrCopy doing special case for deleting message");
String trashFolder = destFolder;
if (K9.FOLDER_NONE.equals(trashFolder)) {
trashFolder = null;
}
remoteSrcFolder.delete(messages, trashFolder);
} else {
remoteDestFolder = remoteStore.getFolder(destFolder);
if (isCopy) {
remoteUidMap = remoteSrcFolder.copyMessages(messages, remoteDestFolder);
} else {
remoteUidMap = remoteSrcFolder.moveMessages(messages, remoteDestFolder);
}
}
if (!isCopy && Expunge.EXPUNGE_IMMEDIATELY == account.getExpungePolicy()) {
Timber.i("processingPendingMoveOrCopy expunging folder %s:%s", account.getDescription(), srcFolder);
List<String> movedUids = new ArrayList<>(messages.size());
for (Message message : messages) {
movedUids.add(message.getUid());
}
remoteSrcFolder.expungeUids(movedUids);
}
/*
* This next part is used to bring the local UIDs of the local destination folder
* upto speed with the remote UIDs of remote destination folder.
*/
if (newUidMap != null && remoteUidMap != null && !remoteUidMap.isEmpty()) {
Timber.i("processingPendingMoveOrCopy: changing local uids of %d messages", remoteUidMap.size());
for (Entry<String, String> entry : remoteUidMap.entrySet()) {
String remoteSrcUid = entry.getKey();
String newUid = entry.getValue();
String localDestUid = newUidMap.get(remoteSrcUid);
if (localDestUid == null) {
continue;
}
Message localDestMessage = localDestFolder.getMessage(localDestUid);
if (localDestMessage != null) {
localDestMessage.setUid(newUid);
localDestFolder.changeUid((LocalMessage) localDestMessage);
for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, destFolder, localDestUid, newUid);
}
Message localDestMessage = localDestFolder.getMessage(localDestUid);
if (localDestMessage != null) {
localDestMessage.setUid(newUid);
localDestFolder.changeUid((LocalMessage) localDestMessage);
for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, destFolder, localDestUid, newUid);
}
}
}
} finally {
closeFolder(remoteSrcFolder);
closeFolder(remoteDestFolder);
}
}