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

Major IMAP IDLE rework:

1) Actual message sync on MessagingController uses same connection as
   IDLE for faster and more efficient push operation.  Uses fewer
   connections to the server.

2) More aggressive handling of untagged responses should more reliably
   get flag changes and new messages when many events happen at once.

3) Simplification of new mail notification

4) Push mail now respects the folder visible limit

5) When multiple untagged FETCH responses arrive en bloc, the actual
   message flags and UIDs are fetched with a single request
This commit is contained in:
Daniel Applebaum 2009-11-26 05:10:12 +00:00
parent e4427f4f17
commit fc91603429
5 changed files with 365 additions and 228 deletions

View File

@ -52,6 +52,7 @@ import com.android.email.mail.FetchProfile;
import com.android.email.mail.Flag; import com.android.email.mail.Flag;
import com.android.email.mail.Folder; import com.android.email.mail.Folder;
import com.android.email.mail.Message; import com.android.email.mail.Message;
import com.android.email.mail.MessageRemovalListener;
import com.android.email.mail.MessageRetrievalListener; import com.android.email.mail.MessageRetrievalListener;
import com.android.email.mail.MessagingException; import com.android.email.mail.MessagingException;
import com.android.email.mail.Part; import com.android.email.mail.Part;
@ -1061,7 +1062,7 @@ public class MessagingController implements Runnable
/* /*
* Now we download the actual content of messages. * Now we download the actual content of messages.
*/ */
int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages); int newMessages = downloadMessages(account, remoteFolder, localFolder, remoteMessages, false);
setLocalUnreadCountToRemote(localFolder, remoteFolder, newMessages); setLocalUnreadCountToRemote(localFolder, remoteFolder, newMessages);
@ -1163,22 +1164,14 @@ public class MessagingController implements Runnable
} }
private int downloadMessages(final Account account, final Folder remoteFolder, private int downloadMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder, List<Message> inputMessages) throws MessagingException final LocalFolder localFolder, List<Message> inputMessages, boolean flagSyncOnly) throws MessagingException
{ {
final String folder = remoteFolder.getName(); final String folder = remoteFolder.getName();
ArrayList<Message> syncFlagMessages = new ArrayList<Message>(); ArrayList<Message> syncFlagMessages = new ArrayList<Message>();
ArrayList<Message> unsyncedMessages = new ArrayList<Message>(); List<Message> unsyncedMessages = new ArrayList<Message>();
final AtomicInteger newMessages = new AtomicInteger(0); final AtomicInteger newMessages = new AtomicInteger(0);
int visibleLimit = localFolder.getVisibleLimit();
int listSize = inputMessages.size();
if (listSize > visibleLimit)
{
inputMessages = inputMessages.subList(listSize - visibleLimit, listSize);
}
List<Message> messages = new ArrayList<Message>(inputMessages); List<Message> messages = new ArrayList<Message>(inputMessages);
for (Message message : messages) for (Message message : messages)
@ -1189,35 +1182,38 @@ public class MessagingController implements Runnable
if (localMessage == null) if (localMessage == null)
{ {
if (!message.isSet(Flag.X_DOWNLOADED_FULL) && !message.isSet(Flag.X_DOWNLOADED_PARTIAL)) if (!flagSyncOnly)
{ {
if (Email.DEBUG) if (!message.isSet(Flag.X_DOWNLOADED_FULL) && !message.isSet(Flag.X_DOWNLOADED_PARTIAL))
{ {
Log.v(Email.LOG_TAG, "Message with uid " + message.getUid() + " is not downloaded at all"); if (Email.DEBUG)
}
unsyncedMessages.add(message);
}
else
{
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, "Message with uid " + message.getUid() + " is partially or fully downloaded");
}
// Store the updated message locally
localFolder.appendMessages(new Message[] { message });
localMessage = localFolder.getMessage(message.getUid());
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, message.isSet(Flag.X_DOWNLOADED_FULL));
localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, message.isSet(Flag.X_DOWNLOADED_PARTIAL));
for (MessagingListener l : getListeners())
{
l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage);
if (!localMessage.isSet(Flag.SEEN))
{ {
l.synchronizeMailboxNewMessage(account, folder, localMessage); Log.v(Email.LOG_TAG, "Message with uid " + message.getUid() + " is not downloaded at all");
}
unsyncedMessages.add(message);
}
else
{
if (Email.DEBUG)
{
Log.v(Email.LOG_TAG, "Message with uid " + message.getUid() + " is partially or fully downloaded");
}
// Store the updated message locally
localFolder.appendMessages(new Message[] { message });
localMessage = localFolder.getMessage(message.getUid());
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, message.isSet(Flag.X_DOWNLOADED_FULL));
localMessage.setFlag(Flag.X_DOWNLOADED_PARTIAL, message.isSet(Flag.X_DOWNLOADED_PARTIAL));
for (MessagingListener l : getListeners())
{
l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage);
if (!localMessage.isSet(Flag.SEEN))
{
l.synchronizeMailboxNewMessage(account, folder, localMessage);
}
} }
} }
} }
@ -1251,6 +1247,13 @@ public class MessagingController implements Runnable
* fetch results for newest to oldest. If not, no harm done. * fetch results for newest to oldest. If not, no harm done.
*/ */
Collections.reverse(unsyncedMessages); Collections.reverse(unsyncedMessages);
int visibleLimit = localFolder.getVisibleLimit();
int listSize = unsyncedMessages.size();
if (listSize > visibleLimit)
{
unsyncedMessages = unsyncedMessages.subList(listSize - visibleLimit, listSize);
}
FetchProfile fp = new FetchProfile(); FetchProfile fp = new FetchProfile();
if (remoteFolder.supportsFetchingFlags()) if (remoteFolder.supportsFetchingFlags())
@ -1281,9 +1284,9 @@ public class MessagingController implements Runnable
// Store the new message locally // Store the new message locally
localFolder.appendMessages(new Message[] localFolder.appendMessages(new Message[]
{ {
message message
}); });
if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE)) if (message.getSize() > (MAX_SMALL_MESSAGE_SIZE))
{ {
@ -1556,6 +1559,19 @@ public class MessagingController implements Runnable
} }
Log.i(Email.LOG_TAG, "SYNC: Synced remote messages for folder " + folder + ", " + newMessages.get() + " new messages"); Log.i(Email.LOG_TAG, "SYNC: Synced remote messages for folder " + folder + ", " + newMessages.get() + " new messages");
localFolder.purgeToVisibleLimit(new MessageRemovalListener()
{
@Override
public void messageRemoved(Message message)
{
for (MessagingListener l : getListeners())
{
l.synchronizeMailboxRemovedMessage(account, folder, message);
}
}
});
return newMessages.get(); return newMessages.get();
} }
@ -2072,7 +2088,7 @@ public class MessagingController implements Runnable
} }
String rootCauseMessage = getRootCauseMessage(t); String rootCauseMessage = getRootCauseMessage(t);
log("Error" + "'" + rootCauseMessage + "'"); Log.e(Email.LOG_TAG, "Error " + "'" + rootCauseMessage + "'", t);
Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication); Store localStore = Store.getInstance(account.getLocalStoreUri(), mApplication);
LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName()); LocalFolder localFolder = (LocalFolder)localStore.getFolder(account.getErrorFolderName());
@ -2474,9 +2490,9 @@ public class MessagingController implements Runnable
fp.add(FetchProfile.Item.ENVELOPE); fp.add(FetchProfile.Item.ENVELOPE);
fp.add(FetchProfile.Item.BODY); fp.add(FetchProfile.Item.BODY);
localFolder.fetch(new Message[] localFolder.fetch(new Message[]
{ {
message message
}, fp, null); }, fp, null);
localFolder.close(false); localFolder.close(false);
if (!message.isSet(Flag.SEEN)) if (!message.isSet(Flag.SEEN))
{ {
@ -2753,9 +2769,9 @@ public class MessagingController implements Runnable
(LocalFolder) localStore.getFolder(account.getOutboxFolderName()); (LocalFolder) localStore.getFolder(account.getOutboxFolderName());
localFolder.open(OpenMode.READ_WRITE); localFolder.open(OpenMode.READ_WRITE);
localFolder.appendMessages(new Message[] localFolder.appendMessages(new Message[]
{ {
message message
}); });
Message localMessage = localFolder.getMessage(message.getUid()); Message localMessage = localFolder.getMessage(message.getUid());
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
localFolder.close(false); localFolder.close(false);
@ -3799,9 +3815,9 @@ public class MessagingController implements Runnable
(LocalFolder) localStore.getFolder(account.getDraftsFolderName()); (LocalFolder) localStore.getFolder(account.getDraftsFolderName());
localFolder.open(OpenMode.READ_WRITE); localFolder.open(OpenMode.READ_WRITE);
localFolder.appendMessages(new Message[] localFolder.appendMessages(new Message[]
{ {
message message
}); });
Message localMessage = localFolder.getMessage(message.getUid()); Message localMessage = localFolder.getMessage(message.getUid());
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
@ -3989,15 +4005,15 @@ public class MessagingController implements Runnable
} }
} }
public void messagesFlagsChanged(String folderName, public void messagesFlagsChanged(Folder folder,
List<Message> messages) List<Message> messages)
{ {
controller.messagesArrived(account, folderName, messages, false); controller.messagesArrived(account, folder, messages, true);
} }
public void messagesArrived(String folderName, List<Message> messages) public void messagesArrived(Folder folder, List<Message> messages)
{ {
controller.messagesArrived(account, folderName, messages, true); controller.messagesArrived(account, folder, messages, false);
} }
public void sleep(long millis) public void sleep(long millis)
@ -4152,44 +4168,37 @@ public class MessagingController implements Runnable
} }
public void messagesArrived(final Account account, final String folderName, final List<Message> messages, final boolean doNotify) public void messagesArrived(final Account account, final Folder remoteFolder, final List<Message> messages, final boolean flagSyncOnly)
{ {
Log.i(Email.LOG_TAG, "Got new pushed email messages for account " + account.getDescription() Log.i(Email.LOG_TAG, "Got new pushed email messages for account " + account.getDescription()
+ ", folder " + folderName); + ", folder " + remoteFolder.getName());
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
putBackground("Push messageArrived of account " + account.getDescription() putBackground("Push messageArrived of account " + account.getDescription()
+ ", folder " + folderName, null, new Runnable() + ", folder " + remoteFolder.getName(), null, new Runnable()
{ {
public void run() public void run()
{ {
LocalFolder localFolder = null; LocalFolder localFolder = null;
Folder remoteFolder = null;
try try
{ {
LocalStore localStore = (LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication); LocalStore localStore = (LocalStore) Store.getInstance(account.getLocalStoreUri(), mApplication);
Store remoteStore = Store.getInstance(account.getStoreUri(), mApplication); localFolder= (LocalFolder) localStore.getFolder(remoteFolder.getName());
remoteFolder = remoteStore.getFolder(folderName);
localFolder= (LocalFolder) localStore.getFolder(folderName);
localFolder.open(OpenMode.READ_WRITE); localFolder.open(OpenMode.READ_WRITE);
remoteFolder.open(OpenMode.READ_WRITE); remoteFolder.open(OpenMode.READ_WRITE);
int newCount = downloadMessages(account, remoteFolder, localFolder, messages); int newCount = downloadMessages(account, remoteFolder, localFolder, messages, flagSyncOnly);
setLocalUnreadCountToRemote(localFolder, remoteFolder, messages.size());
localFolder.setLastPush(System.currentTimeMillis()); localFolder.setLastPush(System.currentTimeMillis());
localFolder.setStatus(null); localFolder.setStatus(null);
int unreadMessageCount = account.getUnreadMessageCount(mApplication, mApplication); int unreadMessageCount = account.getUnreadMessageCount(mApplication, mApplication);
if (doNotify && newCount > 0 && unreadMessageCount > 0) Log.i(Email.LOG_TAG, "messagesArrived newCount = " + newCount + ", unreadMessageCount = " + unreadMessageCount);
{ notifyAccount(mApplication, account, newCount, unreadMessageCount);
notifyAccount(mApplication, account, newCount, unreadMessageCount);
}
if (unreadMessageCount == 0)
{
notifyAccount(mApplication, account, newCount, unreadMessageCount);
}
for (MessagingListener l : getListeners()) for (MessagingListener l : getListeners())
{ {
l.folderStatusChanged(account, folderName); l.folderStatusChanged(account, remoteFolder.getName());
l.accountStatusChanged(account, unreadMessageCount); l.accountStatusChanged(account, unreadMessageCount);
} }
@ -4208,7 +4217,7 @@ public class MessagingController implements Runnable
} }
for (MessagingListener l : getListeners()) for (MessagingListener l : getListeners())
{ {
l.synchronizeMailboxFailed(account, folderName, errorMessage); l.synchronizeMailboxFailed(account, remoteFolder.getName(), errorMessage);
} }
addErrorMessage(account, e); addErrorMessage(account, e);
} }
@ -4225,17 +4234,6 @@ public class MessagingController implements Runnable
Log.e(Email.LOG_TAG, "Unable to close localFolder", e); Log.e(Email.LOG_TAG, "Unable to close localFolder", e);
} }
} }
if (remoteFolder != null)
{
try
{
remoteFolder.close(false);
}
catch (Exception e)
{
Log.e(Email.LOG_TAG, "Unable to close remoteFolder", e);
}
}
latch.countDown(); latch.countDown();
} }
@ -4249,7 +4247,7 @@ public class MessagingController implements Runnable
{ {
Log.e(Email.LOG_TAG, "Interrupted while awaiting latch release", e); Log.e(Email.LOG_TAG, "Interrupted while awaiting latch release", e);
} }
Log.i(Email.LOG_TAG, "Latch released"); Log.i(Email.LOG_TAG, "MessagingController.messagesArrivedLatch released");
} }
enum MemorizingState { STARTED, FINISHED, FAILED }; enum MemorizingState { STARTED, FINISHED, FAILED };

View File

@ -0,0 +1,6 @@
package com.android.email.mail;
public interface MessageRemovalListener
{
public void messageRemoved(Message message);
}

View File

@ -6,8 +6,8 @@ public interface PushReceiver
{ {
public void acquireWakeLock(); public void acquireWakeLock();
public void releaseWakeLock(); public void releaseWakeLock();
public void messagesArrived(String folderName, List<Message> mess); public void messagesArrived(Folder folder, List<Message> mess);
public void messagesFlagsChanged(String folderName, List<Message> mess); public void messagesFlagsChanged(Folder folder, List<Message> mess);
public String getPushState(String folderName); public String getPushState(String folderName);
public void pushError(String errorMessage, Exception e); public void pushError(String errorMessage, Exception e);
public void setPushActive(String folderName, boolean enabled); public void setPushActive(String folderName, boolean enabled);

View File

@ -464,6 +464,12 @@ public class ImapStore extends Store
public void open(OpenMode mode) throws MessagingException public void open(OpenMode mode) throws MessagingException
{ {
internalOpen(mode); internalOpen(mode);
if (mMessageCount == -1)
{
throw new MessagingException(
"Did not find message count during open");
}
} }
public List<ImapResponse> internalOpen(OpenMode mode) throws MessagingException public List<ImapResponse> internalOpen(OpenMode mode) throws MessagingException
@ -559,13 +565,8 @@ public class ImapStore extends Store
} }
} }
if (mMessageCount == -1)
{
throw new MessagingException(
"Did not find message count with command '" + command + "'");
}
mExists = true; mExists = true;
return null; return responses;
} }
catch (IOException ioe) catch (IOException ioe)
{ {
@ -594,7 +595,7 @@ public class ImapStore extends Store
{ {
if (mMessageCount != -1) if (mMessageCount != -1)
{ {
// close(); // close();
mMessageCount = -1; mMessageCount = -1;
} }
if (!isOpen()) if (!isOpen())
@ -788,12 +789,12 @@ public class ImapStore extends Store
public Message[] getMessages(int start, int end, MessageRetrievalListener listener) public Message[] getMessages(int start, int end, MessageRetrievalListener listener)
throws MessagingException throws MessagingException
{ {
return getMessages(start, end, false, listener); return getMessages(start, end, false, listener);
} }
protected Message[] getMessages(int start, int end, boolean includeDeleted, MessageRetrievalListener listener)
protected Message[] getMessages(final int start, final int end, final boolean includeDeleted, final MessageRetrievalListener listener)
throws MessagingException throws MessagingException
{ {
if (start < 1 || end < 1 || end < start) if (start < 1 || end < 1 || end < start)
@ -802,22 +803,48 @@ public class ImapStore extends Store
String.format("Invalid message set %d %d", String.format("Invalid message set %d %d",
start, end)); start, end));
} }
ImapSearcher searcher = new ImapSearcher()
{
public List<ImapResponse> search() throws IOException, MessagingException
{
return executeSimpleCommand(String.format("UID SEARCH %d:%d" + (includeDeleted ? "" : " NOT DELETED"), start, end));
}
};
return search(searcher, listener);
}
protected Message[] getMessages(final List<Integer> mesgSeqs, final boolean includeDeleted, final MessageRetrievalListener listener)
throws MessagingException
{
ImapSearcher searcher = new ImapSearcher()
{
public List<ImapResponse> search() throws IOException, MessagingException
{
return executeSimpleCommand(String.format("UID SEARCH %s" + (includeDeleted ? "" : " NOT DELETED"), Utility.combine(mesgSeqs.toArray(), ',')));
}
};
return search(searcher, listener);
}
private Message[] search(ImapSearcher searcher, MessageRetrievalListener listener) throws MessagingException
{
checkOpen(); checkOpen();
ArrayList<Message> messages = new ArrayList<Message>(); ArrayList<Message> messages = new ArrayList<Message>();
try try
{ {
boolean gotSearchValues = false; boolean gotSearchValues = false;
ArrayList<Integer> uids = new ArrayList<Integer>(); ArrayList<Integer> uids = new ArrayList<Integer>();
List<ImapResponse> responses = executeSimpleCommand(String.format("UID SEARCH %d:%d" + (includeDeleted ? "" : " NOT DELETED"), start, end)); List<ImapResponse> responses = searcher.search(); //
for (ImapResponse response : responses) for (ImapResponse response : responses)
{ {
// Log.d(Email.LOG_TAG, "Got search response: " + response.get(0) + ", size " + response.size()); // Log.d(Email.LOG_TAG, "Got search response: " + response.get(0) + ", size " + response.size());
if (response.get(0).equals("SEARCH")) if (response.get(0).equals("SEARCH"))
{ {
gotSearchValues = true; gotSearchValues = true;
for (int i = 1, count = response.size(); i < count; i++) for (int i = 1, count = response.size(); i < count; i++)
{ {
// Log.d(Email.LOG_TAG, "Got search response UID: " + response.getString(i)); // Log.d(Email.LOG_TAG, "Got search response UID: " + response.getString(i));
uids.add(Integer.parseInt(response.getString(i))); uids.add(Integer.parseInt(response.getString(i)));
} }
@ -975,7 +1002,6 @@ public class ImapStore extends Store
do do
{ {
response = mConnection.readResponse(); response = mConnection.readResponse();
handleUntaggedResponse(response);
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "response for fetch: " + response + " for " + getLogId()); Log.v(Email.LOG_TAG, "response for fetch: " + response + " for " + getLogId());
@ -988,7 +1014,8 @@ public class ImapStore extends Store
Message message = messageMap.get(uid); Message message = messageMap.get(uid);
if (message == null) if (message == null)
{ {
Log.w(Email.LOG_TAG, "Do not have message in messageMap for UID " + uid + " for " + getLogId()); Log.d(Email.LOG_TAG, "Do not have message in messageMap for UID " + uid + " for " + getLogId());
handleUntaggedResponse(response);
continue; continue;
} }
if (listener != null) if (listener != null)
@ -1107,6 +1134,10 @@ public class ImapStore extends Store
listener.messageFinished(message, messageNumber, messageMap.size()); listener.messageFinished(message, messageNumber, messageMap.size());
} }
} }
else
{
handleUntaggedResponse(response);
}
while (response.more()); while (response.more());
@ -1467,9 +1498,9 @@ public class ImapStore extends Store
try try
{ {
/* /*
* Try to find the UID of the message we just appended using the * Try to find the UID of the message we just appended using the
* Message-ID header. * Message-ID header.
*/ */
String[] messageIdHeader = message.getHeader("Message-ID"); String[] messageIdHeader = message.getHeader("Message-ID");
if (messageIdHeader == null || messageIdHeader.length == 0) if (messageIdHeader == null || messageIdHeader.length == 0)
@ -1743,9 +1774,9 @@ public class ImapStore extends Store
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED; final boolean secure = mConnectionSecurity == CONNECTION_SECURITY_SSL_REQUIRED;
sslContext.init(null, new TrustManager[] sslContext.init(null, new TrustManager[]
{ {
TrustManagerFactory.get(mHost, secure) TrustManagerFactory.get(mHost, secure)
}, new SecureRandom()); }, new SecureRandom());
mSocket = sslContext.getSocketFactory().createSocket(); mSocket = sslContext.getSocketFactory().createSocket();
mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT); mSocket.connect(socketAddress, SOCKET_CONNECT_TIMEOUT);
} }
@ -1783,10 +1814,10 @@ public class ImapStore extends Store
{ {
if (capability instanceof String) if (capability instanceof String)
{ {
if (Email.DEBUG) // if (Email.DEBUG)
{ // {
Log.v(Email.LOG_TAG, "Saving capability '" + capability + "' for " + getLogId()); // Log.v(Email.LOG_TAG, "Saving capability '" + capability + "' for " + getLogId());
} // }
capabilities.add((String)capability); capabilities.add((String)capability);
} }
} }
@ -1807,9 +1838,9 @@ public class ImapStore extends Store
SSLContext sslContext = SSLContext.getInstance("TLS"); SSLContext sslContext = SSLContext.getInstance("TLS");
boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED; boolean secure = mConnectionSecurity == CONNECTION_SECURITY_TLS_REQUIRED;
sslContext.init(null, new TrustManager[] sslContext.init(null, new TrustManager[]
{ {
TrustManagerFactory.get(mHost, secure) TrustManagerFactory.get(mHost, secure)
}, new SecureRandom()); }, new SecureRandom());
mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort, mSocket = sslContext.getSocketFactory().createSocket(mSocket, mHost, mPort,
true); true);
mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT); mSocket.setSoTimeout(Store.SOCKET_READ_TIMEOUT);
@ -1886,10 +1917,10 @@ public class ImapStore extends Store
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.v(Email.LOG_TAG, "Connection " + getLogId() + " has " + capabilities.size() + " capabilities"); Log.v(Email.LOG_TAG, "Connection " + getLogId() + " has " + capabilities.size() + " capabilities");
for (String capability : capabilities) // for (String capability : capabilities)
{ // {
Log.v(Email.LOG_TAG, "Have capability '" + capability + "' for " + getLogId()); // Log.v(Email.LOG_TAG, "Have capability '" + capability + "' for " + getLogId());
} // }
} }
return capabilities.contains("IDLE"); return capabilities.contains("IDLE");
} }
@ -2223,6 +2254,7 @@ public class ImapStore extends Store
final AtomicBoolean idling = new AtomicBoolean(false); final AtomicBoolean idling = new AtomicBoolean(false);
final AtomicBoolean doneSent = new AtomicBoolean(false); final AtomicBoolean doneSent = new AtomicBoolean(false);
final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME); final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME);
List<ImapResponse> storedUntaggedResponses = new ArrayList<ImapResponse>();
public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver) public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver)
{ {
@ -2265,6 +2297,7 @@ public class ImapStore extends Store
Log.i(Email.LOG_TAG, "Pusher starting for " + getLogId()); Log.i(Email.LOG_TAG, "Pusher starting for " + getLogId());
while (stop.get() != true) while (stop.get() != true)
{ {
try try
{ {
int oldUidNext = -1; int oldUidNext = -1;
@ -2328,14 +2361,25 @@ public class ImapStore extends Store
{ {
if (stop.get() != true) if (stop.get() != true)
{ {
Log.i(Email.LOG_TAG, "About to IDLE for " + getLogId()); List<ImapResponse> untaggedResponses = null;
if (storedUntaggedResponses.size() > 0)
{
Log.i(Email.LOG_TAG, "Processing " + storedUntaggedResponses.size() + " from previous commands for " + getLogId());
untaggedResponses = new ArrayList<ImapResponse>(storedUntaggedResponses);
}
else
{
Log.i(Email.LOG_TAG, "About to IDLE for " + getLogId());
receiver.setPushActive(getName(), true); receiver.setPushActive(getName(), true);
idling.set(true); idling.set(true);
doneSent.set(false); doneSent.set(false);
executeSimpleCommand("IDLE", false, ImapFolderPusher.this); untaggedResponses = executeSimpleCommand("IDLE", false, ImapFolderPusher.this);
idling.set(false); idling.set(false);
receiver.setPushActive(getName(), false);
}
storedUntaggedResponses.clear();
processUntaggedResponses(untaggedResponses);
delayTime.set(NORMAL_DELAY_TIME); delayTime.set(NORMAL_DELAY_TIME);
} }
} }
@ -2343,6 +2387,7 @@ public class ImapStore extends Store
catch (Exception e) catch (Exception e)
{ {
receiver.acquireWakeLock(); receiver.acquireWakeLock();
storedUntaggedResponses.clear();
idling.set(false); idling.set(false);
receiver.setPushActive(getName(), false); receiver.setPushActive(getName(), false);
try try
@ -2372,6 +2417,7 @@ public class ImapStore extends Store
} }
} }
} }
receiver.setPushActive(getName(), false);
try try
{ {
Log.i(Email.LOG_TAG, "Pusher for " + getLogId() + " is exiting"); Log.i(Email.LOG_TAG, "Pusher for " + getLogId() + " is exiting");
@ -2391,39 +2437,50 @@ public class ImapStore extends Store
listeningThread.start(); listeningThread.start();
} }
List<Integer> flagSyncMsgSeqs = new ArrayList<Integer>(); @Override
protected void handleUntaggedResponse(ImapResponse response)
protected List<ImapResponse> handleUntaggedResponses(List<ImapResponse> responses)
{ {
flagSyncMsgSeqs.clear(); if (response.mTag == null && response.size() > 1)
int oldMessageCount = mMessageCount;
super.handleUntaggedResponses(responses);
List<Integer> flagSyncMsgSeqsCopy = new ArrayList<Integer>();
flagSyncMsgSeqsCopy.addAll(flagSyncMsgSeqs);
if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "oldMessageCount = " + oldMessageCount + ", new mMessageCount = " + mMessageCount Object responseType = response.get(1);
+ " for " + getLogId()); if ("FETCH".equals(responseType)
|| "EXPUNGE".equals(responseType)
|| "EXISTS".equals(responseType))
{
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "Storing response " + response + " for later processing");
}
storedUntaggedResponses.add(response);
}
} }
if (oldMessageCount > 0 && mMessageCount > oldMessageCount) }
protected void processUntaggedResponses(List<ImapResponse> responses)
{
int oldMessageCount = mMessageCount;
List<Integer> flagSyncMsgSeqs = new ArrayList<Integer>();
for (ImapResponse response : responses)
{
oldMessageCount += processUntaggedResponse(oldMessageCount, response, flagSyncMsgSeqs);
}
if (oldMessageCount < 0)
{
oldMessageCount = 0;
}
if (mMessageCount > oldMessageCount)
{ {
syncMessages(oldMessageCount + 1, mMessageCount, true); syncMessages(oldMessageCount + 1, mMessageCount, true);
} }
if (Email.DEBUG) if (Email.DEBUG)
{ {
Log.d(Email.LOG_TAG, "There are " + flagSyncMsgSeqsCopy + " messages needing flag sync for " + getLogId()); Log.d(Email.LOG_TAG, "There are " + flagSyncMsgSeqs + " messages needing flag sync for " + getLogId());
} }
// TODO: Identify ranges and call syncMessages on said identified ranges if (flagSyncMsgSeqs.size() > 0)
for (Integer msgSeq : flagSyncMsgSeqsCopy)
{ {
syncMessages(msgSeq, msgSeq, false); syncMessages(flagSyncMsgSeqs);
} }
return responses;
} }
private void syncMessages(int start, int end, boolean newArrivals) private void syncMessages(int start, int end, boolean newArrivals)
@ -2448,9 +2505,32 @@ public class ImapStore extends Store
} }
} }
protected void handleUntaggedResponse(ImapResponse response) private void syncMessages(List<Integer> flagSyncMsgSeqs)
{
try
{
Message[] messageArray = null;
messageArray = getMessages(flagSyncMsgSeqs, true, null);
List<Message> messages = new ArrayList<Message>();
for (Message message : messageArray)
{
messages.add(message);
}
pushMessages(messages, false);
}
catch (Exception e)
{
receiver.pushError("Exception while processing Push untagged responses", e);
}
}
protected int processUntaggedResponse(int oldMessageCount, ImapResponse response, List<Integer> flagSyncMsgSeqs)
{ {
super.handleUntaggedResponse(response); super.handleUntaggedResponse(response);
int messageCountDelta = 0;
if (response.mTag == null && response.size() > 1) if (response.mTag == null && response.size() > 1)
{ {
try try
@ -2463,7 +2543,37 @@ public class ImapStore extends Store
{ {
Log.d(Email.LOG_TAG, "Got untagged FETCH for msgseq " + msgSeq + " for " + getLogId()); Log.d(Email.LOG_TAG, "Got untagged FETCH for msgseq " + msgSeq + " for " + getLogId());
} }
flagSyncMsgSeqs.add(msgSeq); if (flagSyncMsgSeqs.contains(msgSeq) == false)
{
flagSyncMsgSeqs.add(msgSeq);
}
}
if ("EXPUNGE".equals(responseType))
{
int msgSeq = response.getNumber(0);
if (msgSeq <= oldMessageCount)
{
messageCountDelta = -1;
}
if (Email.DEBUG)
{
Log.d(Email.LOG_TAG, "Got untagged EXPUNGE for msgseq " + msgSeq + " for " + getLogId());
}
List<Integer> newSeqs = new ArrayList<Integer>();
Iterator<Integer> flagIter = flagSyncMsgSeqs.iterator();
while (flagIter.hasNext())
{
Integer flagMsg = flagIter.next();
if (flagMsg >= msgSeq)
{
flagIter.remove();
if (flagMsg > msgSeq)
{
newSeqs.add(flagMsg--);
}
}
}
flagSyncMsgSeqs.addAll(newSeqs);
} }
} }
catch (Exception e) catch (Exception e)
@ -2471,6 +2581,7 @@ public class ImapStore extends Store
Log.e(Email.LOG_TAG, "Could not handle untagged FETCH for " + getLogId(), e); Log.e(Email.LOG_TAG, "Could not handle untagged FETCH for " + getLogId(), e);
} }
} }
return messageCountDelta;
} }
@ -2481,11 +2592,11 @@ public class ImapStore extends Store
{ {
if (newArrivals) if (newArrivals)
{ {
receiver.messagesArrived(getName(), messages); receiver.messagesArrived(this, messages);
} }
else else
{ {
receiver.messagesFlagsChanged(getName(), messages); receiver.messagesFlagsChanged(this, messages);
} }
} }
catch (RuntimeException e) catch (RuntimeException e)
@ -2684,4 +2795,8 @@ public class ImapStore extends Store
} }
} }
private interface ImapSearcher
{
List<ImapResponse> search() throws IOException, MessagingException;
}
} }

View File

@ -46,6 +46,7 @@ import com.android.email.mail.FetchProfile;
import com.android.email.mail.Flag; import com.android.email.mail.Flag;
import com.android.email.mail.Folder; import com.android.email.mail.Folder;
import com.android.email.mail.Message; import com.android.email.mail.Message;
import com.android.email.mail.MessageRemovalListener;
import com.android.email.mail.MessageRetrievalListener; import com.android.email.mail.MessageRetrievalListener;
import com.android.email.mail.MessagingException; import com.android.email.mail.MessagingException;
import com.android.email.mail.Part; import com.android.email.mail.Part;
@ -610,9 +611,9 @@ public class LocalStore extends Store implements Serializable
cursor = mDb.rawQuery("SELECT id, unread_count, visible_limit, last_updated, status, push_state, last_pushed FROM folders " cursor = mDb.rawQuery("SELECT id, unread_count, visible_limit, last_updated, status, push_state, last_pushed FROM folders "
+ "where folders.name = ?", + "where folders.name = ?",
new String[] new String[]
{ {
mName mName
}); });
if (cursor.moveToFirst()) if (cursor.moveToFirst())
{ {
@ -705,10 +706,10 @@ public class LocalStore extends Store implements Serializable
throw new MessagingException("Folder " + mName + " already exists."); throw new MessagingException("Folder " + mName + " already exists.");
} }
mDb.execSQL("INSERT INTO folders (name, visible_limit) VALUES (?, ?)", new Object[] mDb.execSQL("INSERT INTO folders (name, visible_limit) VALUES (?, ?)", new Object[]
{ {
mName, mName,
Email.DEFAULT_VISIBLE_LIMIT Email.DEFAULT_VISIBLE_LIMIT
}); });
return true; return true;
} }
@ -719,10 +720,10 @@ public class LocalStore extends Store implements Serializable
throw new MessagingException("Folder " + mName + " already exists."); throw new MessagingException("Folder " + mName + " already exists.");
} }
mDb.execSQL("INSERT INTO folders (name, visible_limit) VALUES (?, ?)", new Object[] mDb.execSQL("INSERT INTO folders (name, visible_limit) VALUES (?, ?)", new Object[]
{ {
mName, mName,
visibleLimit visibleLimit
}); });
return true; return true;
} }
@ -745,9 +746,9 @@ public class LocalStore extends Store implements Serializable
{ {
cursor = mDb.rawQuery("SELECT COUNT(*) FROM messages WHERE messages.folder_id = ?", cursor = mDb.rawQuery("SELECT COUNT(*) FROM messages WHERE messages.folder_id = ?",
new String[] new String[]
{ {
Long.toString(mFolderId) Long.toString(mFolderId)
}); });
cursor.moveToFirst(); cursor.moveToFirst();
int messageCount = cursor.getInt(0); int messageCount = cursor.getInt(0);
return messageCount; return messageCount;
@ -799,6 +800,23 @@ public class LocalStore extends Store implements Serializable
return mVisibleLimit; return mVisibleLimit;
} }
public void purgeToVisibleLimit(MessageRemovalListener listener) throws MessagingException
{
open(OpenMode.READ_WRITE);
Message[] messages = getMessages(null);
for (int i = 0; i < messages.length; i++)
{
if (i >= mVisibleLimit)
{
if (listener != null)
{
listener.messageRemoved(messages[i]);
}
messages[i].setFlag(Flag.X_DESTROYED, true);
}
}
}
public void setVisibleLimit(int visibleLimit) throws MessagingException public void setVisibleLimit(int visibleLimit) throws MessagingException
{ {
@ -1060,19 +1078,19 @@ public class LocalStore extends Store implements Serializable
cursor = mDb.query( cursor = mDb.query(
"attachments", "attachments",
new String[] new String[]
{ {
"id", "id",
"size", "size",
"name", "name",
"mime_type", "mime_type",
"store_data", "store_data",
"content_uri" "content_uri"
}, },
"message_id = ?", "message_id = ?",
new String[] { Long.toString(localMessage.mId) }, new String[] { Long.toString(localMessage.mId) },
null, null,
null, null,
null); null);
while (cursor.moveToNext()) while (cursor.moveToNext())
{ {
@ -1227,9 +1245,9 @@ public class LocalStore extends Store implements Serializable
+ "bcc_list, reply_to_list, attachment_count, internal_date, message_id " + "bcc_list, reply_to_list, attachment_count, internal_date, message_id "
+ "FROM messages " + "WHERE uid = ? " + "AND folder_id = ?", + "FROM messages " + "WHERE uid = ? " + "AND folder_id = ?",
new String[] new String[]
{ {
message.getUid(), Long.toString(mFolderId) message.getUid(), Long.toString(mFolderId)
}); });
if (!cursor.moveToNext()) if (!cursor.moveToNext())
{ {
return null; return null;
@ -1272,9 +1290,9 @@ public class LocalStore extends Store implements Serializable
+ (includeDeleted ? "" : "deleted = 0") + (includeDeleted ? "" : "deleted = 0")
+ " folder_id = ? ORDER BY date DESC" + " folder_id = ? ORDER BY date DESC"
, new String[] , new String[]
{ {
Long.toString(mFolderId) Long.toString(mFolderId)
}); });
int i = 0; int i = 0;
@ -1369,11 +1387,11 @@ public class LocalStore extends Store implements Serializable
message.setUid(Email.LOCAL_UID_PREFIX + UUID.randomUUID().toString()); message.setUid(Email.LOCAL_UID_PREFIX + UUID.randomUUID().toString());
mDb.execSQL("UPDATE messages " + "SET folder_id = ?, uid = ? " + "WHERE id = ?", new Object[] mDb.execSQL("UPDATE messages " + "SET folder_id = ?, uid = ? " + "WHERE id = ?", new Object[]
{ {
lDestFolder.getId(), lDestFolder.getId(),
message.getUid(), message.getUid(),
lMessage.getId() lMessage.getId()
}); });
LocalMessage placeHolder = new LocalMessage(oldUID, this); LocalMessage placeHolder = new LocalMessage(oldUID, this);
placeHolder.setFlagInternal(Flag.DELETED, true); placeHolder.setFlagInternal(Flag.DELETED, true);
@ -1561,27 +1579,27 @@ public class LocalStore extends Store implements Serializable
+ "html_content = ?, text_content = ?, reply_to_list = ?, " + "html_content = ?, text_content = ?, reply_to_list = ?, "
+ "attachment_count = ? WHERE id = ?", + "attachment_count = ? WHERE id = ?",
new Object[] new Object[]
{ {
message.getUid(), message.getUid(),
message.getSubject(), message.getSubject(),
Address.pack(message.getFrom()), Address.pack(message.getFrom()),
message.getSentDate() == null ? System message.getSentDate() == null ? System
.currentTimeMillis() : message.getSentDate() .currentTimeMillis() : message.getSentDate()
.getTime(), .getTime(),
Utility.combine(message.getFlags(), ',').toUpperCase(), Utility.combine(message.getFlags(), ',').toUpperCase(),
mFolderId, mFolderId,
Address.pack(message Address.pack(message
.getRecipients(RecipientType.TO)), .getRecipients(RecipientType.TO)),
Address.pack(message Address.pack(message
.getRecipients(RecipientType.CC)), .getRecipients(RecipientType.CC)),
Address.pack(message Address.pack(message
.getRecipients(RecipientType.BCC)), .getRecipients(RecipientType.BCC)),
html.length() > 0 ? html : null, html.length() > 0 ? html : null,
text.length() > 0 ? text : null, text.length() > 0 ? text : null,
Address.pack(message.getReplyTo()), Address.pack(message.getReplyTo()),
attachments.size(), attachments.size(),
message.mId message.mId
}); });
for (int i = 0, count = attachments.size(); i < count; i++) for (int i = 0, count = attachments.size(); i < count; i++)
{ {
@ -1621,9 +1639,9 @@ public class LocalStore extends Store implements Serializable
{ {
mDb.execSQL("DELETE FROM headers WHERE id = ?", mDb.execSQL("DELETE FROM headers WHERE id = ?",
new Object[] new Object[]
{ {
id id
}); });
} }
/** /**
@ -1802,9 +1820,9 @@ public class LocalStore extends Store implements Serializable
{ {
open(OpenMode.READ_ONLY); open(OpenMode.READ_ONLY);
mDb.execSQL("DELETE FROM messages WHERE folder_id = ? and date < ?", new Object[] mDb.execSQL("DELETE FROM messages WHERE folder_id = ? and date < ?", new Object[]
{ {
Long.toString(mFolderId), new Long(cutoff) Long.toString(mFolderId), new Long(cutoff)
}); });
resetUnreadCount(); resetUnreadCount();
} }
@ -1840,9 +1858,9 @@ public class LocalStore extends Store implements Serializable
deleteAttachments(message.getUid()); deleteAttachments(message.getUid());
} }
mDb.execSQL("DELETE FROM folders WHERE id = ?", new Object[] mDb.execSQL("DELETE FROM folders WHERE id = ?", new Object[]
{ {
Long.toString(mFolderId), Long.toString(mFolderId),
}); });
} }
@Override @Override
@ -2158,18 +2176,18 @@ public class LocalStore extends Store implements Serializable
"reply_to_list = NULL " + "reply_to_list = NULL " +
"WHERE id = ?", "WHERE id = ?",
new Object[] new Object[]
{ {
mId mId
}); });
/* /*
* Delete all of the messages' attachments to save space. * Delete all of the messages' attachments to save space.
*/ */
mDb.execSQL("DELETE FROM attachments WHERE id = ?", mDb.execSQL("DELETE FROM attachments WHERE id = ?",
new Object[] new Object[]
{ {
mId mId
}); });
((LocalFolder)mFolder).deleteHeaders(mId); ((LocalFolder)mFolder).deleteHeaders(mId);
@ -2213,9 +2231,9 @@ public class LocalStore extends Store implements Serializable
* Set the flags on the message. * Set the flags on the message.
*/ */
mDb.execSQL("UPDATE messages " + "SET flags = ? " + " WHERE id = ?", new Object[] mDb.execSQL("UPDATE messages " + "SET flags = ? " + " WHERE id = ?", new Object[]
{ {
Utility.combine(getFlags(), ',').toUpperCase(), mId Utility.combine(getFlags(), ',').toUpperCase(), mId
}); });
} }
} }