From 100867a2317c7c40d362ce9db982ee2875d3f448 Mon Sep 17 00:00:00 2001 From: Andy Stadler <> Date: Fri, 24 Apr 2009 13:25:34 -0700 Subject: [PATCH] AI 147730: 1. Create an API by which a Store can specify its own custom synchronizer code. 2. Refactor (and spell-fix) the core folder synchronizer. Extract the innards that are IMAP/POP specific, leaving common wrapper code in a simpler shell. 3. For each account & folder to sync, check the store and call the specialized sync'er (if provided) or the generic one. BUG=1807499 Automated import of CL 147730 --- .../android/email/MessagingController.java | 753 +++++++++--------- src/com/android/email/mail/Store.java | 8 + .../android/email/mail/StoreSynchronizer.java | 69 ++ .../mail/exchange/ExchangeStoreExample.java | 13 + 4 files changed, 477 insertions(+), 366 deletions(-) create mode 100644 src/com/android/email/mail/StoreSynchronizer.java diff --git a/src/com/android/email/MessagingController.java b/src/com/android/email/MessagingController.java index 288d57193..73555f587 100644 --- a/src/com/android/email/MessagingController.java +++ b/src/com/android/email/MessagingController.java @@ -25,6 +25,7 @@ import com.android.email.mail.MessagingException; import com.android.email.mail.Part; import com.android.email.mail.Sender; import com.android.email.mail.Store; +import com.android.email.mail.StoreSynchronizer; import com.android.email.mail.Folder.FolderType; import com.android.email.mail.Folder.OpenMode; import com.android.email.mail.internet.MimeUtility; @@ -381,9 +382,6 @@ public class MessagingController implements Runnable { * Start background synchronization of the specified folder. * @param account * @param folder - * @param numNewestMessagesToKeep Specifies the number of messages that should be - * considered as part of the window of available messages. This number effectively limits - * the user's view into the mailbox to the newest (numNewestMessagesToKeep) messages. * @param listener */ public void synchronizeMailbox(final Account account, final String folder, @@ -399,393 +397,46 @@ public class MessagingController implements Runnable { } put("synchronizeMailbox", listener, new Runnable() { public void run() { - synchronizeMailboxSyncronous(account, folder); + synchronizeMailboxSynchronous(account, folder); } }); } /** - * Start foreground synchronization of the specified folder. This is generally only called - * by synchronizeMailbox. + * Start foreground synchronization of the specified folder. This is called by + * synchronizeMailbox or checkMail. * @param account * @param folder - * @param numNewestMessagesToKeep Specifies the number of messages that should be - * considered as part of the window of available messages. This number effectively limits - * the user's view into the mailbox to the newest (numNewestMessagesToKeep) messages. * @param listener - * - * TODO Break this method up into smaller chunks. */ - public void synchronizeMailboxSyncronous(final Account account, final String folder) { + private void synchronizeMailboxSynchronous(final Account account, final String folder) { for (MessagingListener l : mListeners) { l.synchronizeMailboxStarted(account, folder); } try { processPendingCommandsSynchronous(account); - /* - * Get the message list from the local store and create an index of - * the uids within the list. - */ - final LocalStore localStore = - (LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication, null); - final LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder); - localFolder.open(OpenMode.READ_WRITE, null); - Message[] localMessages = localFolder.getMessages(null); - HashMap localUidMap = new HashMap(); - for (Message message : localMessages) { - localUidMap.put(message.getUid(), message); - } + StoreSynchronizer.SyncResults results; + // Select generic sync or store-specific sync Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication, account.getStoreCallbacks()); - Folder remoteFolder = remoteStore.getFolder(folder); - - /* - * If the folder is a "special" folder we need to see if it exists - * on the remote server. It if does not exist we'll try to create it. If we - * can't create we'll abort. This will happen on every single Pop3 folder as - * designed and on Imap folders during error conditions. This allows us - * to treat Pop3 and Imap the same in this code. - */ - if (folder.equals(account.getTrashFolderName()) || - folder.equals(account.getSentFolderName()) || - folder.equals(account.getDraftsFolderName())) { - if (!remoteFolder.exists()) { - if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) { - for (MessagingListener l : mListeners) { - l.synchronizeMailboxFinished(account, folder, 0, 0); - } - return; - } - } + StoreSynchronizer customSync = remoteStore.getMessageSynchronizer(); + if (customSync == null) { + results = synchronizeMailboxGeneric(account, folder); + } else { + results = customSync.SynchronizeMessagesSynchronous( + account, folder, mListeners, mApplication); } - - /* - * Synchronization process: - Open the folder - Upload any local messages that are marked as PENDING_UPLOAD (Drafts, Sent, Trash) - Get the message count - Get the list of the newest Email.DEFAULT_VISIBLE_LIMIT messages - getMessages(messageCount - Email.DEFAULT_VISIBLE_LIMIT, messageCount) - See if we have each message locally, if not fetch it's flags and envelope - Get and update the unread count for the folder - Update the remote flags of any messages we have locally with an internal date - newer than the remote message. - Get the current flags for any messages we have locally but did not just download - Update local flags - For any message we have locally but not remotely, delete the local message to keep - cache clean. - Download larger parts of any new messages. - (Optional) Download small attachments in the background. - */ - - /* - * Open the remote folder. This pre-loads certain metadata like message count. - */ - remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks()); - - /* - * Trash any remote messages that are marked as trashed locally. - */ - - /* - * Get the remote message count. - */ - int remoteMessageCount = remoteFolder.getMessageCount(); - - int visibleLimit = localFolder.getVisibleLimit(); - if (visibleLimit <= 0) { - Store.StoreInfo info = Store.StoreInfo.getStoreInfo(account.getStoreUri(), - mApplication); - visibleLimit = info.mVisibleLimitDefault; - localFolder.setVisibleLimit(visibleLimit); - } - - Message[] remoteMessages = new Message[0]; - final ArrayList unsyncedMessages = new ArrayList(); - HashMap remoteUidMap = new HashMap(); - - if (remoteMessageCount > 0) { - /* - * Message numbers start at 1. - */ - int remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1; - int remoteEnd = remoteMessageCount; - remoteMessages = remoteFolder.getMessages(remoteStart, remoteEnd, null); - for (Message message : remoteMessages) { - remoteUidMap.put(message.getUid(), message); - } - - /* - * Get a list of the messages that are in the remote list but not on the - * local store, or messages that are in the local store but failed to download - * on the last sync. These are the new messages that we will download. - */ - for (Message message : remoteMessages) { - Message localMessage = localUidMap.get(message.getUid()); - if (localMessage == null || - (!localMessage.isSet(Flag.X_DOWNLOADED_FULL) && - !localMessage.isSet(Flag.X_DOWNLOADED_PARTIAL))) { - unsyncedMessages.add(message); - } - } - } - - /* - * A list of messages that were downloaded and which did not have the Seen flag set. - * This will serve to indicate the true "new" message count that will be reported to - * the user via notification. - */ - final ArrayList newMessages = new ArrayList(); - - /* - * Fetch the flags and envelope only of the new messages. This is intended to get us - * critical data as fast as possible, and then we'll fill in the details. - */ - if (unsyncedMessages.size() > 0) { - - /* - * Reverse the order of the messages. Depending on the server this may get us - * fetch results for newest to oldest. If not, no harm done. - */ - Collections.reverse(unsyncedMessages); - - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.FLAGS); - fp.add(FetchProfile.Item.ENVELOPE); - remoteFolder.fetch(unsyncedMessages.toArray(new Message[0]), fp, - new MessageRetrievalListener() { - public void messageFinished(Message message, int number, int ofTotal) { - try { - // Store the new message locally - localFolder.appendMessages(new Message[] { - message - }); - - // And include it in the view - if (message.getSubject() != null && - message.getFrom() != null) { - /* - * We check to make sure that we got something worth - * showing (subject and from) because some protocols - * (POP) may not be able to give us headers for - * ENVELOPE, only size. - */ - for (MessagingListener l : mListeners) { - l.synchronizeMailboxNewMessage(account, folder, - localFolder.getMessage(message.getUid())); - } - } - - if (!message.isSet(Flag.SEEN)) { - newMessages.add(message); - } - } - catch (Exception e) { - Log.e(Email.LOG_TAG, - "Error while storing downloaded message.", - e); - } - } - - public void messageStarted(String uid, int number, int ofTotal) { - } - }); - } - - /* - * Refresh the flags for any messages in the local store that we didn't just - * download. - */ - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.FLAGS); - remoteFolder.fetch(remoteMessages, fp, null); - for (Message remoteMessage : remoteMessages) { - Message localMessage = localFolder.getMessage(remoteMessage.getUid()); - if (localMessage == null) { - continue; - } - if (remoteMessage.isSet(Flag.SEEN) != localMessage.isSet(Flag.SEEN)) { - localMessage.setFlag(Flag.SEEN, remoteMessage.isSet(Flag.SEEN)); - for (MessagingListener l : mListeners) { - l.synchronizeMailboxNewMessage(account, folder, localMessage); - } - } - } - - /* - * Get and store the unread message count. - */ - int remoteUnreadMessageCount = remoteFolder.getUnreadMessageCount(); - if (remoteUnreadMessageCount == -1) { - localFolder.setUnreadMessageCount(localFolder.getUnreadMessageCount() - + newMessages.size()); - } - else { - localFolder.setUnreadMessageCount(remoteUnreadMessageCount); - } - - /* - * Remove any messages that are in the local store but no longer on the remote store. - */ - for (Message localMessage : localMessages) { - if (remoteUidMap.get(localMessage.getUid()) == null) { - localMessage.setFlag(Flag.X_DESTROYED, true); - for (MessagingListener l : mListeners) { - l.synchronizeMailboxRemovedMessage(account, folder, localMessage); - } - } - } - - /* - * Now we download the actual content of messages. - */ - ArrayList largeMessages = new ArrayList(); - ArrayList smallMessages = new ArrayList(); - for (Message message : unsyncedMessages) { - /* - * Sort the messages into two buckets, small and large. Small messages will be - * downloaded fully and large messages will be downloaded in parts. By sorting - * into two buckets we can pipeline the commands for each set of messages - * into a single command to the server saving lots of round trips. - */ - if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) { - largeMessages.add(message); - } else { - smallMessages.add(message); - } - } - /* - * Grab the content of the small messages first. This is going to - * be very fast and at very worst will be a single up of a few bytes and a single - * download of 625k. - */ - fp = new FetchProfile(); - fp.add(FetchProfile.Item.BODY); - remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), - fp, new MessageRetrievalListener() { - public void messageFinished(Message message, int number, int ofTotal) { - try { - // Store the updated message locally - localFolder.appendMessages(new Message[] { - message - }); - - Message localMessage = localFolder.getMessage(message.getUid()); - - // Set a flag indicating this message has now be fully downloaded - localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); - - // Update the listener with what we've found - for (MessagingListener l : mListeners) { - l.synchronizeMailboxNewMessage( - account, - folder, - localMessage); - } - } - catch (MessagingException me) { - - } - } - - public void messageStarted(String uid, int number, int ofTotal) { - } - }); - - /* - * Now do the large messages that require more round trips. - */ - fp.clear(); - fp.add(FetchProfile.Item.STRUCTURE); - remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), - fp, null); - for (Message message : largeMessages) { - if (message.getBody() == null) { - /* - * The provider was unable to get the structure of the message, so - * we'll download a reasonable portion of the messge and mark it as - * incomplete so the entire thing can be downloaded later if the user - * wishes to download it. - */ - fp.clear(); - fp.add(FetchProfile.Item.BODY_SANE); - /* - * TODO a good optimization here would be to make sure that all Stores set - * the proper size after this fetch and compare the before and after size. If - * they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED - */ - - remoteFolder.fetch(new Message[] { message }, fp, null); - // Store the updated message locally - localFolder.appendMessages(new Message[] { - message - }); - - Message localMessage = localFolder.getMessage(message.getUid()); - - // Set a flag indicating that the message has been partially downloaded and - // is ready for view. - localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, true); - } else { - /* - * We have a structure to deal with, from which - * we can pull down the parts we want to actually store. - * Build a list of parts we are interested in. Text parts will be downloaded - * right now, attachments will be left for later. - */ - - ArrayList viewables = new ArrayList(); - ArrayList attachments = new ArrayList(); - MimeUtility.collectParts(message, viewables, attachments); - - /* - * Now download the parts we're interested in storing. - */ - for (Part part : viewables) { - fp.clear(); - fp.add(part); - // TODO what happens if the network connection dies? We've got partial - // messages with incorrect status stored. - remoteFolder.fetch(new Message[] { message }, fp, null); - } - // Store the updated message locally - localFolder.appendMessages(new Message[] { - message - }); - - Message localMessage = localFolder.getMessage(message.getUid()); - - // Set a flag indicating this message has been fully downloaded and can be - // viewed. - localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); - } - - // Update the listener with what we've found - for (MessagingListener l : mListeners) { - l.synchronizeMailboxNewMessage( - account, - folder, - localFolder.getMessage(message.getUid())); - } - } - - - /* - * Notify listeners that we're finally done. - */ + for (MessagingListener l : mListeners) { l.synchronizeMailboxFinished( account, folder, - remoteFolder.getMessageCount(), newMessages.size()); + results.mTotalMessages, results.mNewMessages); } - remoteFolder.close(false); - localFolder.close(false); - } - catch (Exception e) { + } catch (Exception e) { if (Config.LOGV) { Log.v(Email.LOG_TAG, "synchronizeMailbox", e); } @@ -797,6 +448,376 @@ public class MessagingController implements Runnable { } } } + + /** + * Generic synchronizer - used for POP3 and IMAP. + * + * TODO Break this method up into smaller chunks. + * + * @param account + * @param folder + * @return + * @throws MessagingException + */ + private StoreSynchronizer.SyncResults synchronizeMailboxGeneric(final Account account, + final String folder) throws MessagingException { + /* + * Get the message list from the local store and create an index of + * the uids within the list. + */ + final LocalStore localStore = + (LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication, null); + final LocalFolder localFolder = (LocalFolder) localStore.getFolder(folder); + localFolder.open(OpenMode.READ_WRITE, null); + Message[] localMessages = localFolder.getMessages(null); + HashMap localUidMap = new HashMap(); + for (Message message : localMessages) { + localUidMap.put(message.getUid(), message); + } + + Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication, + account.getStoreCallbacks()); + Folder remoteFolder = remoteStore.getFolder(folder); + + /* + * If the folder is a "special" folder we need to see if it exists + * on the remote server. It if does not exist we'll try to create it. If we + * can't create we'll abort. This will happen on every single Pop3 folder as + * designed and on Imap folders during error conditions. This allows us + * to treat Pop3 and Imap the same in this code. + */ + if (folder.equals(account.getTrashFolderName()) || + folder.equals(account.getSentFolderName()) || + folder.equals(account.getDraftsFolderName())) { + if (!remoteFolder.exists()) { + if (!remoteFolder.create(FolderType.HOLDS_MESSAGES)) { + return new StoreSynchronizer.SyncResults(0, 0); + } + } + } + + /* + * Synchronization process: + Open the folder + Upload any local messages that are marked as PENDING_UPLOAD (Drafts, Sent, Trash) + Get the message count + Get the list of the newest Email.DEFAULT_VISIBLE_LIMIT messages + getMessages(messageCount - Email.DEFAULT_VISIBLE_LIMIT, messageCount) + See if we have each message locally, if not fetch it's flags and envelope + Get and update the unread count for the folder + Update the remote flags of any messages we have locally with an internal date + newer than the remote message. + Get the current flags for any messages we have locally but did not just download + Update local flags + For any message we have locally but not remotely, delete the local message to keep + cache clean. + Download larger parts of any new messages. + (Optional) Download small attachments in the background. + */ + + /* + * Open the remote folder. This pre-loads certain metadata like message count. + */ + remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks()); + + /* + * Trash any remote messages that are marked as trashed locally. + */ + + /* + * Get the remote message count. + */ + int remoteMessageCount = remoteFolder.getMessageCount(); + + int visibleLimit = localFolder.getVisibleLimit(); + if (visibleLimit <= 0) { + Store.StoreInfo info = Store.StoreInfo.getStoreInfo(account.getStoreUri(), + mApplication); + visibleLimit = info.mVisibleLimitDefault; + localFolder.setVisibleLimit(visibleLimit); + } + + Message[] remoteMessages = new Message[0]; + final ArrayList unsyncedMessages = new ArrayList(); + HashMap remoteUidMap = new HashMap(); + + if (remoteMessageCount > 0) { + /* + * Message numbers start at 1. + */ + int remoteStart = Math.max(0, remoteMessageCount - visibleLimit) + 1; + int remoteEnd = remoteMessageCount; + remoteMessages = remoteFolder.getMessages(remoteStart, remoteEnd, null); + for (Message message : remoteMessages) { + remoteUidMap.put(message.getUid(), message); + } + + /* + * Get a list of the messages that are in the remote list but not on the + * local store, or messages that are in the local store but failed to download + * on the last sync. These are the new messages that we will download. + */ + for (Message message : remoteMessages) { + Message localMessage = localUidMap.get(message.getUid()); + if (localMessage == null || + (!localMessage.isSet(Flag.X_DOWNLOADED_FULL) && + !localMessage.isSet(Flag.X_DOWNLOADED_PARTIAL))) { + unsyncedMessages.add(message); + } + } + } + + /* + * A list of messages that were downloaded and which did not have the Seen flag set. + * This will serve to indicate the true "new" message count that will be reported to + * the user via notification. + */ + final ArrayList newMessages = new ArrayList(); + + /* + * Fetch the flags and envelope only of the new messages. This is intended to get us + * critical data as fast as possible, and then we'll fill in the details. + */ + if (unsyncedMessages.size() > 0) { + + /* + * Reverse the order of the messages. Depending on the server this may get us + * fetch results for newest to oldest. If not, no harm done. + */ + Collections.reverse(unsyncedMessages); + + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.FLAGS); + fp.add(FetchProfile.Item.ENVELOPE); + remoteFolder.fetch(unsyncedMessages.toArray(new Message[0]), fp, + new MessageRetrievalListener() { + public void messageFinished(Message message, int number, int ofTotal) { + try { + // Store the new message locally + localFolder.appendMessages(new Message[] { + message + }); + + // And include it in the view + if (message.getSubject() != null && + message.getFrom() != null) { + /* + * We check to make sure that we got something worth + * showing (subject and from) because some protocols + * (POP) may not be able to give us headers for + * ENVELOPE, only size. + */ + for (MessagingListener l : mListeners) { + l.synchronizeMailboxNewMessage(account, folder, + localFolder.getMessage(message.getUid())); + } + } + + if (!message.isSet(Flag.SEEN)) { + newMessages.add(message); + } + } + catch (Exception e) { + Log.e(Email.LOG_TAG, + "Error while storing downloaded message.", + e); + } + } + + public void messageStarted(String uid, int number, int ofTotal) { + } + }); + } + + /* + * Refresh the flags for any messages in the local store that we didn't just + * download. + */ + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.FLAGS); + remoteFolder.fetch(remoteMessages, fp, null); + for (Message remoteMessage : remoteMessages) { + Message localMessage = localFolder.getMessage(remoteMessage.getUid()); + if (localMessage == null) { + continue; + } + if (remoteMessage.isSet(Flag.SEEN) != localMessage.isSet(Flag.SEEN)) { + localMessage.setFlag(Flag.SEEN, remoteMessage.isSet(Flag.SEEN)); + for (MessagingListener l : mListeners) { + l.synchronizeMailboxNewMessage(account, folder, localMessage); + } + } + } + + /* + * Get and store the unread message count. + */ + int remoteUnreadMessageCount = remoteFolder.getUnreadMessageCount(); + if (remoteUnreadMessageCount == -1) { + localFolder.setUnreadMessageCount(localFolder.getUnreadMessageCount() + + newMessages.size()); + } + else { + localFolder.setUnreadMessageCount(remoteUnreadMessageCount); + } + + /* + * Remove any messages that are in the local store but no longer on the remote store. + */ + for (Message localMessage : localMessages) { + if (remoteUidMap.get(localMessage.getUid()) == null) { + localMessage.setFlag(Flag.X_DESTROYED, true); + for (MessagingListener l : mListeners) { + l.synchronizeMailboxRemovedMessage(account, folder, localMessage); + } + } + } + + /* + * Now we download the actual content of messages. + */ + ArrayList largeMessages = new ArrayList(); + ArrayList smallMessages = new ArrayList(); + for (Message message : unsyncedMessages) { + /* + * Sort the messages into two buckets, small and large. Small messages will be + * downloaded fully and large messages will be downloaded in parts. By sorting + * into two buckets we can pipeline the commands for each set of messages + * into a single command to the server saving lots of round trips. + */ + if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) { + largeMessages.add(message); + } else { + smallMessages.add(message); + } + } + /* + * Grab the content of the small messages first. This is going to + * be very fast and at very worst will be a single up of a few bytes and a single + * download of 625k. + */ + fp = new FetchProfile(); + fp.add(FetchProfile.Item.BODY); + remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), + fp, new MessageRetrievalListener() { + public void messageFinished(Message message, int number, int ofTotal) { + try { + // Store the updated message locally + localFolder.appendMessages(new Message[] { + message + }); + + Message localMessage = localFolder.getMessage(message.getUid()); + + // Set a flag indicating this message has now be fully downloaded + localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); + + // Update the listener with what we've found + for (MessagingListener l : mListeners) { + l.synchronizeMailboxNewMessage( + account, + folder, + localMessage); + } + } + catch (MessagingException me) { + + } + } + + public void messageStarted(String uid, int number, int ofTotal) { + } + }); + + /* + * Now do the large messages that require more round trips. + */ + fp.clear(); + fp.add(FetchProfile.Item.STRUCTURE); + remoteFolder.fetch(largeMessages.toArray(new Message[largeMessages.size()]), + fp, null); + for (Message message : largeMessages) { + if (message.getBody() == null) { + /* + * The provider was unable to get the structure of the message, so + * we'll download a reasonable portion of the messge and mark it as + * incomplete so the entire thing can be downloaded later if the user + * wishes to download it. + */ + fp.clear(); + fp.add(FetchProfile.Item.BODY_SANE); + /* + * TODO a good optimization here would be to make sure that all Stores set + * the proper size after this fetch and compare the before and after size. If + * they equal we can mark this SYNCHRONIZED instead of PARTIALLY_SYNCHRONIZED + */ + + remoteFolder.fetch(new Message[] { message }, fp, null); + // Store the updated message locally + localFolder.appendMessages(new Message[] { + message + }); + + Message localMessage = localFolder.getMessage(message.getUid()); + + // Set a flag indicating that the message has been partially downloaded and + // is ready for view. + localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, true); + } else { + /* + * We have a structure to deal with, from which + * we can pull down the parts we want to actually store. + * Build a list of parts we are interested in. Text parts will be downloaded + * right now, attachments will be left for later. + */ + + ArrayList viewables = new ArrayList(); + ArrayList attachments = new ArrayList(); + MimeUtility.collectParts(message, viewables, attachments); + + /* + * Now download the parts we're interested in storing. + */ + for (Part part : viewables) { + fp.clear(); + fp.add(part); + // TODO what happens if the network connection dies? We've got partial + // messages with incorrect status stored. + remoteFolder.fetch(new Message[] { message }, fp, null); + } + // Store the updated message locally + localFolder.appendMessages(new Message[] { + message + }); + + Message localMessage = localFolder.getMessage(message.getUid()); + + // Set a flag indicating this message has been fully downloaded and can be + // viewed. + localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); + } + + // Update the listener with what we've found + for (MessagingListener l : mListeners) { + l.synchronizeMailboxNewMessage( + account, + folder, + localFolder.getMessage(message.getUid())); + } + } + + + /* + * Report successful sync + */ + StoreSynchronizer.SyncResults results = new StoreSynchronizer.SyncResults( + remoteFolder.getMessageCount(), newMessages.size()); + + remoteFolder.close(false); + localFolder.close(false); + + return results; + } private void queuePendingCommand(Account account, PendingCommand command) { try { @@ -1509,7 +1530,7 @@ public class MessagingController implements Runnable { } for (Account account : accounts) { sendPendingMessagesSynchronous(account); - synchronizeMailboxSyncronous(account, Email.INBOX); + synchronizeMailboxSynchronous(account, Email.INBOX); } for (MessagingListener l : mListeners) { l.checkMailFinished(context, null); // TODO this needs to pass the actual array diff --git a/src/com/android/email/mail/Store.java b/src/com/android/email/mail/Store.java index 041814e3e..b8cd41d51 100644 --- a/src/com/android/email/mail/Store.java +++ b/src/com/android/email/mail/Store.java @@ -189,6 +189,14 @@ public abstract class Store { return com.android.email.activity.setup.AccountSetupIncoming.class; } + /** + * Get class of sync'er for this Store class + * @return Message Sync controller, or null to use default + */ + public StoreSynchronizer getMessageSynchronizer() { + return null; + } + public abstract Folder getFolder(String name) throws MessagingException; public abstract Folder[] getPersonalNamespaces() throws MessagingException; diff --git a/src/com/android/email/mail/StoreSynchronizer.java b/src/com/android/email/mail/StoreSynchronizer.java new file mode 100644 index 000000000..f9ce99c98 --- /dev/null +++ b/src/com/android/email/mail/StoreSynchronizer.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2009 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.android.email.mail; + +import com.android.email.Account; +import com.android.email.MessagingListener; + +import android.content.Context; + +import java.util.Collection; + +/** + * This interface allows a store to define a completely different synchronizer algorithm, + * as necessary. + */ +public interface StoreSynchronizer { + + /** + * An object of this class is returned by SynchronizeMessagesSynchronous to report + * the results of the sync run. + */ + public static class SyncResults { + /** + * The total # of messages in the folder + */ + public int mTotalMessages; + /** + * The # of new messages in the folder + */ + public int mNewMessages; + + public SyncResults(int totalMessages, int newMessages) { + mTotalMessages = totalMessages; + mNewMessages = newMessages; + } + } + + /** + * The job of this method is to synchronize messages between a remote folder and the + * corresponding local folder. + * + * The following callbacks should be called during this operation: + * {@link MessagingListener#synchronizeMailboxNewMessage(Account, String, Message)} + * {@link MessagingListener#synchronizeMailboxRemovedMessage(Account, String, Message)} + * + * @param account The account to synchronize + * @param folder The folder to synchronize + * @param listeners callbacks to make during sync operation + * @param context if needed for making system calls + * @return an object describing the sync results + */ + public SyncResults SynchronizeMessagesSynchronous(Account account, String folder, + Collection listeners, Context context) throws MessagingException; + +} diff --git a/src/com/android/email/mail/exchange/ExchangeStoreExample.java b/src/com/android/email/mail/exchange/ExchangeStoreExample.java index 2b6af2d96..45ba4fae7 100644 --- a/src/com/android/email/mail/exchange/ExchangeStoreExample.java +++ b/src/com/android/email/mail/exchange/ExchangeStoreExample.java @@ -20,6 +20,7 @@ import com.android.email.Email; import com.android.email.mail.Folder; import com.android.email.mail.MessagingException; import com.android.email.mail.Store; +import com.android.email.mail.StoreSynchronizer; import android.content.Context; import android.util.Config; @@ -151,5 +152,17 @@ public class ExchangeStoreExample extends Store { public Class getSettingActivityClass() { return com.android.email.activity.setup.AccountSetupExchange.class; } + + /** + * Get class of sync'er for this Store class. Because exchange Sync rules are so different + * than IMAP or POP3, it's likely that an Exchange implementation will need its own sync + * controller. If so, this function must return a non-null value. + * + * @return Message Sync controller, or null to use default + */ + @Override + public StoreSynchronizer getMessageSynchronizer() { + return null; + } }