From 15f3ba6219056ab45053c68119e1f6e83c1c6507 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Wed, 10 Aug 2016 13:43:26 +0200 Subject: [PATCH] serialize PendingCommands as JSON --- k9mail/build.gradle | 1 + .../k9/controller/MessagingController.java | 355 ++++-------------- .../MessagingControllerCommands.java | 137 +++++++ .../controller/PendingCommandSerializer.java | 70 ++++ .../com/fsck/k9/mailstore/LocalStore.java | 50 +-- .../k9/mailstore/StoreSchemaDefinition.java | 2 +- .../mailstore/migrations/MigrationTo56.java | 14 + .../k9/mailstore/migrations/Migrations.java | 3 + 8 files changed, 305 insertions(+), 327 deletions(-) create mode 100644 k9mail/src/main/java/com/fsck/k9/controller/MessagingControllerCommands.java create mode 100644 k9mail/src/main/java/com/fsck/k9/controller/PendingCommandSerializer.java create mode 100644 k9mail/src/main/java/com/fsck/k9/mailstore/migrations/MigrationTo56.java diff --git a/k9mail/build.gradle b/k9mail/build.gradle index c868605bd..0ec745af7 100644 --- a/k9mail/build.gradle +++ b/k9mail/build.gradle @@ -31,6 +31,7 @@ dependencies { compile 'com.splitwise:tokenautocomplete:2.0.7' compile 'de.cketti.safecontentresolver:safe-content-resolver-v14:0.9.0' compile 'com.github.amlcurran.showcaseview:library:5.4.1' + compile 'com.squareup.moshi:moshi:1.2.0' androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.1' 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 a1ec80e87..c74492dae 100644 --- a/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java +++ b/k9mail/src/main/java/com/fsck/k9/controller/MessagingController.java @@ -56,6 +56,13 @@ import com.fsck.k9.R; import com.fsck.k9.activity.MessageReference; import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection; import com.fsck.k9.cache.EmailProviderCache; +import com.fsck.k9.controller.MessagingControllerCommands.PendingAppend; +import com.fsck.k9.controller.MessagingControllerCommands.PendingCommand; +import com.fsck.k9.controller.MessagingControllerCommands.PendingEmptyTrash; +import com.fsck.k9.controller.MessagingControllerCommands.PendingExpunge; +import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead; +import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy; +import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag; import com.fsck.k9.helper.Contacts; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.AuthenticationFailedException; @@ -86,7 +93,6 @@ import com.fsck.k9.mailstore.LocalFolder; import com.fsck.k9.mailstore.LocalFolder.MoreMessages; import com.fsck.k9.mailstore.LocalMessage; import com.fsck.k9.mailstore.LocalStore; -import com.fsck.k9.mailstore.LocalStore.PendingCommand; import com.fsck.k9.mailstore.MessageRemovalListener; import com.fsck.k9.mailstore.UnavailableStorageException; import com.fsck.k9.notification.NotificationController; @@ -115,19 +121,8 @@ import com.fsck.k9.search.SqlQueryBuilder; public class MessagingController { public static final long INVALID_MESSAGE_ID = -1; - private static final String[] EMPTY_STRING_ARRAY = new String[0]; private static final Set SYNC_FLAGS = EnumSet.of(Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED); - 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"; - private static final String PENDING_COMMAND_APPEND = "com.fsck.k9.MessagingController.append"; - private static final String PENDING_COMMAND_MARK_ALL_AS_READ = "com.fsck.k9.MessagingController.markAllAsRead"; - private static final String PENDING_COMMAND_EXPUNGE = "com.fsck.k9.MessagingController.expunge"; - private static MessagingController inst = null; @@ -1726,13 +1721,11 @@ public class MessagingController { try { for (PendingCommand command : commands) { processingCommand = command; - if (K9.DEBUG) + if (K9.DEBUG) { Log.d(K9.LOG_TAG, "Processing pending command '" + command + "'"); - - String[] components = command.command.split("\\."); - String commandTitle = components[components.length - 1]; + } for (MessagingListener l : getListeners()) { - l.pendingCommandStarted(account, commandTitle); + l.pendingCommandStarted(account, command.getCommandName()); } /* * We specifically do not catch any exceptions here. If a command fails it is @@ -1740,28 +1733,23 @@ public class MessagingController { * other command processes. This maintains the order of the commands. */ try { - if (PENDING_COMMAND_APPEND.equals(command.command)) { - processPendingAppend(command, account); - } else if (PENDING_COMMAND_SET_FLAG_BULK.equals(command.command)) { - processPendingSetFlag(command, account); - } else if (PENDING_COMMAND_SET_FLAG.equals(command.command)) { - processPendingSetFlagOld(command, account); - } 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); - } else if (PENDING_COMMAND_EMPTY_TRASH.equals(command.command)) { - processPendingEmptyTrash(command, account); - } else if (PENDING_COMMAND_EXPUNGE.equals(command.command)) { - processPendingExpunge(command, account); + if (command instanceof PendingAppend) { + processPendingAppend((PendingAppend) command, account); + } else if (command instanceof PendingSetFlag) { + processPendingSetFlag((PendingSetFlag) command, account); + } else if (command instanceof PendingMarkAllAsRead) { + processPendingMarkAllAsRead((PendingMarkAllAsRead) command, account); + } else if (command instanceof PendingMoveOrCopy) { + processPendingMoveOrCopy((PendingMoveOrCopy) command, account); + } else if (command instanceof PendingEmptyTrash) { + processPendingEmptyTrash((PendingEmptyTrash) command, account); + } else if (command instanceof PendingExpunge) { + processPendingExpunge((PendingExpunge) command, account); } localStore.removePendingCommand(command); - if (K9.DEBUG) + if (K9.DEBUG) { Log.d(K9.LOG_TAG, "Done processing pending command '" + command + "'"); + } } catch (MessagingException me) { if (me.isPermanentFailure()) { addErrorMessage(account, null, me); @@ -1774,7 +1762,7 @@ public class MessagingController { progress++; for (MessagingListener l : getListeners()) { l.synchronizeMailboxProgress(account, null, progress, todo); - l.pendingCommandCompleted(account, commandTitle); + l.pendingCommandCompleted(account, command.getCommandName()); } } } @@ -1798,13 +1786,13 @@ public class MessagingController { * created. * TODO update the local message UID instead of deleteing it */ - private void processPendingAppend(PendingCommand command, Account account) throws MessagingException { + private void processPendingAppend(PendingAppend command, Account account) throws MessagingException { Folder remoteFolder = null; LocalFolder localFolder = null; try { - String folder = command.arguments[0]; - String uid = command.arguments[1]; + String folder = command.folder; + String uid = command.uid; if (account.getErrorFolderName().equals(folder)) { return; @@ -1924,16 +1912,7 @@ public class MessagingController { if (account.getErrorFolderName().equals(srcFolder)) { return; } - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW; - - int length = 4 + uids.length; - command.arguments = new String[length]; - command.arguments[0] = srcFolder; - command.arguments[1] = destFolder; - command.arguments[2] = Boolean.toString(isCopy); - command.arguments[3] = Boolean.toString(false); - System.arraycopy(uids, 0, command.arguments, 4, uids.length); + PendingCommand command = MessagingControllerCommands.createMoveOrCopyBulk(srcFolder, destFolder, isCopy, uids); queuePendingCommand(account, command); } @@ -1945,72 +1924,22 @@ public class MessagingController { 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(EMPTY_STRING_ARRAY), 0, command.arguments, 4, uidMap.keySet().size()); - System.arraycopy(uidMap.values().toArray(EMPTY_STRING_ARRAY), 0, command.arguments, 4 + uidMap.keySet().size(), uidMap.values().size()); + PendingCommand command = MessagingControllerCommands.createMoveOrCopyBulk(srcFolder, destFolder, isCopy, uidMap); queuePendingCommand(account, command); } } - /** - * Convert pending command to new format and call - * {@link #processPendingMoveOrCopy(PendingCommand, Account)}. - * - *

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

- * - * @param command - * Pending move/copy command in old format. - * @param account - * The account the pending command belongs to. - * - * @throws MessagingException - * In case of an error. - */ - private void processPendingMoveOrCopyOld2(PendingCommand command, Account account) throws MessagingException { - PendingCommand newCommand = new PendingCommand(); - int len = command.arguments.length; - newCommand.command = PENDING_COMMAND_MOVE_OR_COPY_BULK_NEW; - newCommand.arguments = new String[len + 1]; - newCommand.arguments[0] = command.arguments[0]; - newCommand.arguments[1] = command.arguments[1]; - newCommand.arguments[2] = command.arguments[2]; - newCommand.arguments[3] = Boolean.toString(false); - System.arraycopy(command.arguments, 3, newCommand.arguments, 4, len - 3); - - processPendingMoveOrCopy(newCommand, account); - } - - /** - * Process a pending trash message command. - */ - private void processPendingMoveOrCopy(PendingCommand command, Account account) throws MessagingException { + private void processPendingMoveOrCopy(PendingMoveOrCopy command, Account account) throws MessagingException { Folder remoteSrcFolder = null; Folder remoteDestFolder = null; LocalFolder localDestFolder; try { - String srcFolder = command.arguments[0]; + String srcFolder = command.srcFolder; if (account.getErrorFolderName().equals(srcFolder)) { return; } - 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); - } + String destFolder = command.destFolder; + boolean isCopy = command.isCopy; Store remoteStore = account.getRemoteStore(); remoteSrcFolder = remoteStore.getFolder(srcFolder); @@ -2019,34 +1948,11 @@ public class MessagingController { localDestFolder = (LocalFolder) localStore.getFolder(destFolder); List messages = new ArrayList<>(); - /* - * We split up the localUidMap into two parts while sending the command, here we assemble it back. - */ - Map localUidMap = new HashMap<>(); - if (hasNewUids) { - int offset = (command.arguments.length - 4) / 2; - - for (int i = 4; i < 4 + offset; i++) { - localUidMap.put(command.arguments[i], command.arguments[i + offset]); - - String uid = command.arguments[i]; - if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { - messages.add(remoteSrcFolder.getMessage(uid)); - } + Collection uids = command.newUidMap != null ? command.newUidMap.keySet() : Arrays.asList(command.uids); + for (String uid : uids) { + 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); } if (!remoteSrcFolder.exists()) { @@ -2092,11 +1998,14 @@ public class MessagingController { * 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 (!localUidMap.isEmpty() && remoteUidMap != null && !remoteUidMap.isEmpty()) { + if (command.newUidMap != null && remoteUidMap != null && !remoteUidMap.isEmpty()) { for (Map.Entry entry : remoteUidMap.entrySet()) { String remoteSrcUid = entry.getKey(); - String localDestUid = localUidMap.get(remoteSrcUid); String newUid = entry.getValue(); + String localDestUid = command.newUidMap.get(remoteSrcUid); + if (localDestUid == null) { + continue; + } Message localDestMessage = localDestFolder.getMessage(localDestUid); if (localDestMessage != null) { @@ -2115,18 +2024,11 @@ public class MessagingController { } private void queueSetFlag(final Account account, final String folderName, - final String newState, final String flag, final String[] uids) { + final boolean newState, final Flag flag, final String[] uids) { putBackground("queueSetFlag " + account.getDescription() + ":" + folderName, null, new Runnable() { @Override public void run() { - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_SET_FLAG_BULK; - int length = 3 + uids.length; - command.arguments = new String[length]; - command.arguments[0] = folderName; - command.arguments[1] = newState; - command.arguments[2] = flag; - System.arraycopy(uids, 0, command.arguments, 3, uids.length); + PendingCommand command = MessagingControllerCommands.createSetFlag(folderName, newState, flag, uids); queuePendingCommand(account, command); processPendingCommands(account); } @@ -2135,16 +2037,15 @@ public class MessagingController { /** * Processes a pending mark read or unread command. */ - private void processPendingSetFlag(PendingCommand command, Account account) throws MessagingException { - String folder = command.arguments[0]; + private void processPendingSetFlag(PendingSetFlag command, Account account) throws MessagingException { + String folder = command.folder; if (account.getErrorFolderName().equals(folder)) { return; } - boolean newState = Boolean.parseBoolean(command.arguments[1]); - - Flag flag = Flag.valueOf(command.arguments[2]); + boolean newState = command.newState; + Flag flag = command.flag; Store remoteStore = account.getRemoteStore(); Folder remoteFolder = remoteStore.getFolder(folder); @@ -2158,8 +2059,7 @@ public class MessagingController { return; } List messages = new ArrayList<>(); - for (int i = 3; i < command.arguments.length; i++) { - String uid = command.arguments[i]; + for (String uid : command.uids) { if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { messages.add(remoteFolder.getMessage(uid)); } @@ -2174,61 +2074,18 @@ public class MessagingController { } } - // TODO: This method is obsolete and is only for transition from K-9 2.0 to K-9 2.1 - // Eventually, it should be removed - private void processPendingSetFlagOld(PendingCommand command, Account account) throws MessagingException { - String folder = command.arguments[0]; - String uid = command.arguments[1]; - - if (account.getErrorFolderName().equals(folder)) { - return; - } - if (K9.DEBUG) - Log.d(K9.LOG_TAG, "processPendingSetFlagOld: folder = " + folder + ", uid = " + uid); - - boolean newState = Boolean.parseBoolean(command.arguments[2]); - - Flag flag = Flag.valueOf(command.arguments[3]); - Folder remoteFolder = null; - try { - Store remoteStore = account.getRemoteStore(); - remoteFolder = remoteStore.getFolder(folder); - if (!remoteFolder.exists()) { - return; - } - remoteFolder.open(Folder.OPEN_MODE_RW); - if (remoteFolder.getMode() != Folder.OPEN_MODE_RW) { - return; - } - Message remoteMessage = null; - if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { - remoteMessage = remoteFolder.getMessage(uid); - } - if (remoteMessage == null) { - return; - } - remoteMessage.setFlag(flag, newState); - } finally { - closeFolder(remoteFolder); - } - } private void queueExpunge(final Account account, final String folderName) { putBackground("queueExpunge " + account.getDescription() + ":" + folderName, null, new Runnable() { @Override public void run() { - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_EXPUNGE; - - command.arguments = new String[1]; - - command.arguments[0] = folderName; + PendingCommand command = MessagingControllerCommands.createExpunge(folderName); queuePendingCommand(account, command); processPendingCommands(account); } }); } - private void processPendingExpunge(PendingCommand command, Account account) throws MessagingException { - String folder = command.arguments[0]; + private void processPendingExpunge(PendingExpunge command, Account account) throws MessagingException { + String folder = command.folder; if (account.getErrorFolderName().equals(folder)) { return; @@ -2254,73 +2111,8 @@ public class MessagingController { } } - - // TODO: This method is obsolete and is only for transition from K-9 2.0 to K-9 2.1 - // Eventually, it should be removed - private void processPendingMoveOrCopyOld(PendingCommand command, Account account) throws MessagingException { - String srcFolder = command.arguments[0]; - String uid = command.arguments[1]; - String destFolder = command.arguments[2]; - String isCopyS = command.arguments[3]; - - boolean isCopy = false; - if (isCopyS != null) { - isCopy = Boolean.parseBoolean(isCopyS); - } - - if (account.getErrorFolderName().equals(srcFolder)) { - return; - } - - Store remoteStore = account.getRemoteStore(); - Folder remoteSrcFolder = remoteStore.getFolder(srcFolder); - Folder remoteDestFolder = remoteStore.getFolder(destFolder); - - if (!remoteSrcFolder.exists()) { - throw new MessagingException("processPendingMoveOrCopyOld: remoteFolder " + srcFolder + " does not exist", true); - } - remoteSrcFolder.open(Folder.OPEN_MODE_RW); - if (remoteSrcFolder.getMode() != Folder.OPEN_MODE_RW) { - throw new MessagingException("processPendingMoveOrCopyOld: could not open remoteSrcFolder " + srcFolder + " read/write", true); - } - - Message remoteMessage = null; - if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { - remoteMessage = remoteSrcFolder.getMessage(uid); - } - if (remoteMessage == null) { - throw new MessagingException("processPendingMoveOrCopyOld: remoteMessage " + uid + " does not exist", true); - } - - if (K9.DEBUG) - Log.d(K9.LOG_TAG, "processPendingMoveOrCopyOld: source folder = " + srcFolder - + ", uid = " + uid + ", destination folder = " + destFolder + ", isCopy = " + isCopy); - - if (!isCopy && destFolder.equals(account.getTrashFolderName())) { - if (K9.DEBUG) - Log.d(K9.LOG_TAG, "processPendingMoveOrCopyOld doing special case for deleting message"); - - remoteMessage.delete(account.getTrashFolderName()); - remoteSrcFolder.close(); - return; - } - - remoteDestFolder.open(Folder.OPEN_MODE_RW); - if (remoteDestFolder.getMode() != Folder.OPEN_MODE_RW) { - throw new MessagingException("processPendingMoveOrCopyOld: could not open remoteDestFolder " + srcFolder + " read/write", true); - } - - if (isCopy) { - remoteSrcFolder.copyMessages(Collections.singletonList(remoteMessage), remoteDestFolder); - } else { - remoteSrcFolder.moveMessages(Collections.singletonList(remoteMessage), remoteDestFolder); - } - remoteSrcFolder.close(); - remoteDestFolder.close(); - } - - private void processPendingMarkAllAsRead(PendingCommand command, Account account) throws MessagingException { - String folder = command.arguments[0]; + private void processPendingMarkAllAsRead(PendingMarkAllAsRead command, Account account) throws MessagingException { + String folder = command.folder; Folder remoteFolder = null; LocalFolder localFolder = null; try { @@ -2443,9 +2235,7 @@ public class MessagingController { if (K9.DEBUG) { Log.i(K9.LOG_TAG, "Marking all messages in " + account.getDescription() + ":" + folder + " as read"); } - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_MARK_ALL_AS_READ; - command.arguments = new String[] { folder }; + PendingCommand command = MessagingControllerCommands.createMarkAllAsRead(folder); queuePendingCommand(account, command); processPendingCommands(account); } @@ -2534,7 +2324,7 @@ public class MessagingController { // Send flag change to server List value = entry.getValue(); String[] uids = value.toArray(new String[value.size()]); - queueSetFlag(account, folderName, Boolean.toString(newState), flag.toString(), uids); + queueSetFlag(account, folderName, newState, flag, uids); processPendingCommands(account); } } @@ -2602,7 +2392,7 @@ public class MessagingController { uids[i] = messages.get(i).getUid(); } - queueSetFlag(account, folderName, Boolean.toString(newState), flag.toString(), uids); + queueSetFlag(account, folderName, newState, flag, uids); processPendingCommands(account); } catch (MessagingException me) { addErrorMessage(account, null, me); @@ -3034,9 +2824,7 @@ public class MessagingController { if (K9.DEBUG) Log.i(K9.LOG_TAG, "Moved sent message to folder '" + account.getSentFolderName() + "' (" + localSentFolder.getId() + ") "); - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_APPEND; - command.arguments = new String[] { localSentFolder.getName(), message.getUid() }; + PendingCommand command = MessagingControllerCommands.createAppend(localSentFolder.getName(), message.getUid()); queuePendingCommand(account, command); processPendingCommands(account); } @@ -3635,25 +3423,19 @@ public class MessagingController { for (Message message : messages) { // If the message was in the Outbox, then it has been copied to local Trash, and has // to be copied to remote trash - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_APPEND; - command.arguments = - new String[] { - account.getTrashFolderName(), - message.getUid() - }; + PendingCommand command = MessagingControllerCommands.createAppend(account.getTrashFolderName(), message.getUid()); queuePendingCommand(account, command); } processPendingCommands(account); } else if (account.getDeletePolicy() == DeletePolicy.ON_DELETE) { if (folder.equals(account.getTrashFolderName())) { - queueSetFlag(account, folder, Boolean.toString(true), Flag.DELETED.toString(), uids); + queueSetFlag(account, folder, true, Flag.DELETED, uids); } else { queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids, uidMap); } processPendingCommands(account); } else if (account.getDeletePolicy() == DeletePolicy.MARK_AS_READ) { - queueSetFlag(account, folder, Boolean.toString(true), Flag.SEEN.toString(), uids); + queueSetFlag(account, folder, true, Flag.SEEN, uids); processPendingCommands(account); } else { if (K9.DEBUG) @@ -3683,7 +3465,7 @@ public class MessagingController { } @SuppressWarnings("UnusedParameters") // for consistency with other PendingCommand methods - private void processPendingEmptyTrash(PendingCommand command, Account account) throws MessagingException { + private void processPendingEmptyTrash(PendingEmptyTrash command, Account account) throws MessagingException { Store remoteStore = account.getRemoteStore(); Folder remoteFolder = remoteStore.getFolder(account.getTrashFolderName()); @@ -3729,9 +3511,7 @@ public class MessagingController { } if (!isTrashLocalOnly) { - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_EMPTY_TRASH; - command.arguments = EMPTY_STRING_ARRAY; + PendingCommand command = MessagingControllerCommands.createEmptyTrash(); queuePendingCommand(account, command); processPendingCommands(account); } @@ -4224,12 +4004,7 @@ public class MessagingController { localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); if (saveRemotely) { - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_APPEND; - command.arguments = new String[] { - localFolder.getName(), - localMessage.getUid() - }; + PendingCommand command = MessagingControllerCommands.createAppend(localFolder.getName(), localMessage.getUid()); queuePendingCommand(account, command); processPendingCommands(account); } diff --git a/k9mail/src/main/java/com/fsck/k9/controller/MessagingControllerCommands.java b/k9mail/src/main/java/com/fsck/k9/controller/MessagingControllerCommands.java new file mode 100644 index 000000000..ef8c1bf55 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/controller/MessagingControllerCommands.java @@ -0,0 +1,137 @@ +package com.fsck.k9.controller; + + +import java.util.Map; + +import com.fsck.k9.mail.Flag; + + +public class MessagingControllerCommands { + static final String COMMAND_APPEND = "append"; + static final String COMMAND_MARK_ALL_AS_READ = "mark_all_as_read"; + static final String COMMAND_SET_FLAG = "set_flag"; + static final String COMMAND_EXPUNGE = "expunge"; + static final String COMMAND_MOVE_OR_COPY = "move_or_copy"; + static final String COMMAND_EMPTY_TRASH = "empty_trash"; + + + public static PendingSetFlag createSetFlag(String folder, boolean newState, Flag flag, String[] uids) { + return new PendingSetFlag(folder, newState, flag, uids); + } + + public static PendingExpunge createExpunge(String folderName) { + return new PendingExpunge(folderName); + } + + public static PendingMarkAllAsRead createMarkAllAsRead(String folder) { + return new PendingMarkAllAsRead(folder); + } + + public static PendingAppend createAppend(String folderName, String uid) { + return new PendingAppend(folderName, uid); + } + + public static PendingEmptyTrash createEmptyTrash() { + return new PendingEmptyTrash(); + } + + public static PendingMoveOrCopy createMoveOrCopyBulk(String srcFolder, String destFolder, boolean isCopy, Map uidMap) { + return new PendingMoveOrCopy(srcFolder, destFolder, isCopy, null, uidMap); + } + + public static PendingMoveOrCopy createMoveOrCopyBulk(String srcFolder, String destFolder, boolean isCopy, String[] uids) { + return new PendingMoveOrCopy(srcFolder, destFolder, isCopy, uids, null); + } + + + public static abstract class PendingCommand { + public transient long databaseId; + + public abstract String getCommandName(); + + private PendingCommand() { } + } + + public static class PendingMoveOrCopy extends PendingCommand { + public final String srcFolder; + public final String destFolder; + public final boolean isCopy; + public final String[] uids; + public final Map newUidMap; + + public PendingMoveOrCopy( + String srcFolder, String destFolder, boolean isCopy, String[] uids, Map newUidMap) { + this.srcFolder = srcFolder; + this.destFolder = destFolder; + this.isCopy = isCopy; + this.uids = uids; + this.newUidMap = newUidMap; + } + + public String getCommandName() { + return COMMAND_MOVE_OR_COPY; + } + } + + public static class PendingEmptyTrash extends PendingCommand { + public String getCommandName() { + return COMMAND_EMPTY_TRASH; + } + } + + public static class PendingSetFlag extends PendingCommand { + public final String folder; + public final boolean newState; + public final Flag flag; + public final String[] uids; + + public PendingSetFlag(String folder, boolean newState, Flag flag, String[] uids) { + this.folder = folder; + this.newState = newState; + this.flag = flag; + this.uids = uids; + } + + public String getCommandName() { + return COMMAND_SET_FLAG; + } + } + + public static class PendingAppend extends PendingCommand { + public final String folder; + public final String uid; + + public PendingAppend(String folder, String uid) { + this.folder = folder; + this.uid = uid; + } + + public String getCommandName() { + return COMMAND_APPEND; + } + } + + public static class PendingMarkAllAsRead extends PendingCommand { + public final String folder; + + public PendingMarkAllAsRead(String folder) { + this.folder = folder; + } + + public String getCommandName() { + return COMMAND_MARK_ALL_AS_READ; + } + } + + public static class PendingExpunge extends PendingCommand { + public final String folder; + + public PendingExpunge(String folder) { + this.folder = folder; + } + + public String getCommandName() { + return COMMAND_EXPUNGE; + } + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/controller/PendingCommandSerializer.java b/k9mail/src/main/java/com/fsck/k9/controller/PendingCommandSerializer.java new file mode 100644 index 000000000..27beff3d7 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/controller/PendingCommandSerializer.java @@ -0,0 +1,70 @@ +package com.fsck.k9.controller; + + +import java.io.IOError; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import com.fsck.k9.controller.MessagingControllerCommands.PendingAppend; +import com.fsck.k9.controller.MessagingControllerCommands.PendingCommand; +import com.fsck.k9.controller.MessagingControllerCommands.PendingEmptyTrash; +import com.fsck.k9.controller.MessagingControllerCommands.PendingExpunge; +import com.fsck.k9.controller.MessagingControllerCommands.PendingMarkAllAsRead; +import com.fsck.k9.controller.MessagingControllerCommands.PendingMoveOrCopy; +import com.fsck.k9.controller.MessagingControllerCommands.PendingSetFlag; +import com.squareup.moshi.JsonAdapter; +import com.squareup.moshi.Moshi; + + +public class PendingCommandSerializer { + private static final PendingCommandSerializer INSTANCE = new PendingCommandSerializer(); + + + private final Map> adapters; + + + private PendingCommandSerializer() { + Moshi moshi = new Moshi.Builder().build(); + HashMap> adapters = new HashMap<>(); + + adapters.put(MessagingControllerCommands.COMMAND_MOVE_OR_COPY, moshi.adapter(PendingMoveOrCopy.class)); + adapters.put(MessagingControllerCommands.COMMAND_APPEND, moshi.adapter(PendingAppend.class)); + adapters.put(MessagingControllerCommands.COMMAND_EMPTY_TRASH, moshi.adapter(PendingEmptyTrash.class)); + adapters.put(MessagingControllerCommands.COMMAND_EXPUNGE, moshi.adapter(PendingExpunge.class)); + adapters.put(MessagingControllerCommands.COMMAND_MARK_ALL_AS_READ, moshi.adapter(PendingMarkAllAsRead.class)); + adapters.put(MessagingControllerCommands.COMMAND_SET_FLAG, moshi.adapter(PendingSetFlag.class)); + + this.adapters = Collections.unmodifiableMap(adapters); + } + + + public static PendingCommandSerializer getInstance() { + return INSTANCE; + } + + + public String serialize(T command) { + // noinspection unchecked, we know the map has correctly matching adapters + JsonAdapter adapter = (JsonAdapter) adapters.get(command.getCommandName()); + if (adapter == null) { + throw new IllegalArgumentException("Unsupported pending command type!"); + } + return adapter.toJson(command); + } + + public PendingCommand unserialize(long databaseId, String commandName, String data) { + JsonAdapter adapter = adapters.get(commandName); + if (adapter == null) { + throw new IllegalArgumentException("Unsupported pending command type!"); + } + try { + PendingCommand command = adapter.fromJson(data); + command.databaseId = databaseId; + return command; + } catch (IOException e) { + throw new IOError(e); + } + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalStore.java b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalStore.java index 267b4e6da..0bde477fc 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/LocalStore.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/LocalStore.java @@ -32,7 +32,8 @@ import android.util.Log; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.Preferences; -import com.fsck.k9.helper.UrlEncodingHelper; +import com.fsck.k9.controller.PendingCommandSerializer; +import com.fsck.k9.controller.MessagingControllerCommands.PendingCommand; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Folder; @@ -134,7 +135,7 @@ public class LocalStore extends Store implements Serializable { */ private static final int THREAD_FLAG_UPDATE_BATCH_SIZE = 500; - public static final int DB_VERSION = 55; + public static final int DB_VERSION = 56; public static String getColumnNameForFlag(Flag flag) { @@ -169,6 +170,7 @@ public class LocalStore extends Store implements Serializable { private final MessagePreviewCreator messagePreviewCreator; private final MessageFulltextCreator messageFulltextCreator; private final AttachmentCounter attachmentCounter; + private final PendingCommandSerializer pendingCommandSerializer; /** * local://localhost/path/to/database/uuid.db @@ -187,6 +189,7 @@ public class LocalStore extends Store implements Serializable { messagePreviewCreator = MessagePreviewCreator.newInstance(); messageFulltextCreator = MessageFulltextCreator.newInstance(); attachmentCounter = AttachmentCounter.newInstance(); + pendingCommandSerializer = PendingCommandSerializer.getInstance(); database.open(); } @@ -464,7 +467,7 @@ public class LocalStore extends Store implements Serializable { Cursor cursor = null; try { cursor = db.query("pending_commands", - new String[] { "id", "command", "arguments" }, + new String[] { "id", "command", "data" }, null, null, null, @@ -472,14 +475,11 @@ public class LocalStore extends Store implements Serializable { "id ASC"); List commands = new ArrayList<>(); while (cursor.moveToNext()) { - PendingCommand command = new PendingCommand(); - command.mId = cursor.getLong(0); - command.command = cursor.getString(1); - String arguments = cursor.getString(2); - command.arguments = arguments.split(","); - for (int i = 0; i < command.arguments.length; i++) { - command.arguments[i] = Utility.fastUrlDecode(command.arguments[i]); - } + long databaseId = cursor.getLong(0); + String commandName = cursor.getString(1); + String data = cursor.getString(2); + PendingCommand command = pendingCommandSerializer.unserialize( + databaseId, commandName, data); commands.add(command); } return commands; @@ -491,12 +491,9 @@ public class LocalStore extends Store implements Serializable { } public void addPendingCommand(PendingCommand command) throws MessagingException { - for (int i = 0; i < command.arguments.length; i++) { - command.arguments[i] = UrlEncodingHelper.encodeUtf8(command.arguments[i]); - } final ContentValues cv = new ContentValues(); - cv.put("command", command.command); - cv.put("arguments", Utility.combine(command.arguments, ',')); + cv.put("command", command.getCommandName()); + cv.put("data", pendingCommandSerializer.serialize(command)); database.execute(false, new DbCallback() { @Override public Void doDbWork(final SQLiteDatabase db) throws WrappedException { @@ -510,7 +507,7 @@ public class LocalStore extends Store implements Serializable { database.execute(false, new DbCallback() { @Override public Void doDbWork(final SQLiteDatabase db) throws WrappedException { - db.delete("pending_commands", "id = ?", new String[] { Long.toString(command.mId) }); + db.delete("pending_commands", "id = ?", new String[] { Long.toString(command.databaseId) }); return null; } }); @@ -526,25 +523,6 @@ public class LocalStore extends Store implements Serializable { }); } - public static class PendingCommand { - private long mId; - public String command; - public String[] arguments; - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(command); - sb.append(": "); - for (String argument : arguments) { - sb.append(", "); - sb.append(argument); - //sb.append("\n"); - } - return sb.toString(); - } - } - @Override public boolean isMoveCapable() { return true; diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/StoreSchemaDefinition.java b/k9mail/src/main/java/com/fsck/k9/mailstore/StoreSchemaDefinition.java index 50afda705..f14000209 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/StoreSchemaDefinition.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/StoreSchemaDefinition.java @@ -195,7 +195,7 @@ class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition { db.execSQL("DROP TABLE IF EXISTS pending_commands"); db.execSQL("CREATE TABLE pending_commands " + - "(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)"); + "(id INTEGER PRIMARY KEY, command TEXT, data TEXT)"); db.execSQL("DROP TRIGGER IF EXISTS delete_folder"); db.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;"); diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/migrations/MigrationTo56.java b/k9mail/src/main/java/com/fsck/k9/mailstore/migrations/MigrationTo56.java new file mode 100644 index 000000000..2b0554418 --- /dev/null +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/migrations/MigrationTo56.java @@ -0,0 +1,14 @@ +package com.fsck.k9.mailstore.migrations; + + +import android.database.sqlite.SQLiteDatabase; + + +public class MigrationTo56 { + public static void migratePendingCommands(SQLiteDatabase db) { + // TODO actually migrate + db.execSQL("DROP TABLE IF EXISTS pending_commands"); + db.execSQL("CREATE TABLE pending_commands " + + "(id INTEGER PRIMARY KEY, command TEXT, data TEXT)"); + } +} diff --git a/k9mail/src/main/java/com/fsck/k9/mailstore/migrations/Migrations.java b/k9mail/src/main/java/com/fsck/k9/mailstore/migrations/Migrations.java index 4c8aec597..ca61590b7 100644 --- a/k9mail/src/main/java/com/fsck/k9/mailstore/migrations/Migrations.java +++ b/k9mail/src/main/java/com/fsck/k9/mailstore/migrations/Migrations.java @@ -64,6 +64,9 @@ public class Migrations { MigrationTo54.addPreviewTypeColumn(db); case 54: MigrationTo55.createFtsSearchTable(db, migrationsHelper); + case 55: + MigrationTo56.migratePendingCommands(db); + } } }