1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-08-13 17:03:48 -04:00

Implement windowing for IMAP UID FETCH

Our previous implementation of UID FETCH didn't ever take into account
maximum command line lengths. When fetching, say 800 messages from a
GMail IMAP server, we could easily overflow the max line length leading
to a fetch that didn't get all the messages we wanted to and was
truncated before the description of which fields we want. That caused
K-9 to fetch complete messages, exhaust memory and ultimately fail,
even when we were just trying to get message lengths.

An equivalent fix needs to be made to seach by UID.
This commit is contained in:
Jesse Vincent 2010-08-11 03:39:29 +00:00
parent f9fd5a8e4c
commit af45eae2ce

View File

@ -70,6 +70,8 @@ public class ImapStore extends Store
private static int MAX_DELAY_TIME = 5 * 60 * 1000; // 5 minutes private static int MAX_DELAY_TIME = 5 * 60 * 1000; // 5 minutes
private static int NORMAL_DELAY_TIME = 5000; private static int NORMAL_DELAY_TIME = 5000;
private static int FETCH_WINDOW_SIZE = 100;
private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN }; private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.SEEN };
private static final String CAPABILITY_IDLE = "IDLE"; private static final String CAPABILITY_IDLE = "IDLE";
@ -1155,12 +1157,14 @@ public class ImapStore extends Store
return; return;
} }
checkOpen(); checkOpen();
String[] uids = new String[messages.length]; List<String> uids = new ArrayList<String>(messages.length);
HashMap<String, Message> messageMap = new HashMap<String, Message>(); HashMap<String, Message> messageMap = new HashMap<String, Message>();
for (int i = 0, count = messages.length; i < count; i++) for (int i = 0, count = messages.length; i < count; i++)
{ {
uids[i] = messages[i].getUid();
messageMap.put(uids[i], messages[i]); String uid = messages[i].getUid();
uids.add(uid);
messageMap.put(uid, messages[i]);
} }
/* /*
@ -1195,101 +1199,108 @@ public class ImapStore extends Store
fetchFields.add("BODY.PEEK[]"); fetchFields.add("BODY.PEEK[]");
} }
try
for (int windowStart=1; windowStart <= messages.length; windowStart += (FETCH_WINDOW_SIZE +1))
{ {
mConnection.sendCommand(String.format("UID FETCH %s (%s)", List<String> uidWindow = uids.subList(windowStart, Math.min((windowStart+FETCH_WINDOW_SIZE),messages.length));
Utility.combine(uids, ','),
Utility.combine(fetchFields.toArray(new String[fetchFields.size()]), ' ')
), false);
ImapResponse response;
int messageNumber = 0;
ImapResponseParser.IImapResponseCallback callback = null; try
if (fp.contains(FetchProfile.Item.BODY) || fp.contains(FetchProfile.Item.BODY_SANE) || fp.contains(FetchProfile.Item.ENVELOPE))
{ {
callback = new FetchBodyCallback(messageMap); mConnection.sendCommand(String.format("UID FETCH %s (%s)",
} Utility.combine(uidWindow.toArray(new String[uidWindow.size()]), ','),
Utility.combine(fetchFields.toArray(new String[fetchFields.size()]), ' ')
), false);
ImapResponse response;
int messageNumber = 0;
do ImapResponseParser.IImapResponseCallback callback = null;
{ if (fp.contains(FetchProfile.Item.BODY) || fp.contains(FetchProfile.Item.BODY_SANE) || fp.contains(FetchProfile.Item.ENVELOPE))
response = mConnection.readResponse(callback);
if (response.mTag == null && ImapResponseParser.equalsIgnoreCase(response.get(1), "FETCH"))
{ {
ImapList fetchList = (ImapList)response.getKeyedValue("FETCH"); callback = new FetchBodyCallback(messageMap);
String uid = fetchList.getKeyedString("UID"); }
int msgSeq = response.getNumber(0);
if (uid != null) do
{
response = mConnection.readResponse(callback);
if (response.mTag == null && ImapResponseParser.equalsIgnoreCase(response.get(1), "FETCH"))
{ {
try ImapList fetchList = (ImapList)response.getKeyedValue("FETCH");
String uid = fetchList.getKeyedString("UID");
int msgSeq = response.getNumber(0);
if (uid != null)
{ {
msgSeqUidMap.put(msgSeq, uid); try
if (K9.DEBUG)
{ {
Log.v(K9.LOG_TAG, "Stored uid '" + uid + "' for msgSeq " + msgSeq + " into map " /*+ msgSeqUidMap.toString() */); msgSeqUidMap.put(msgSeq, uid);
if (K9.DEBUG)
{
Log.v(K9.LOG_TAG, "Stored uid '" + uid + "' for msgSeq " + msgSeq + " into map " /*+ msgSeqUidMap.toString() */);
}
}
catch (Exception e)
{
Log.e(K9.LOG_TAG, "Unable to store uid '" + uid + "' for msgSeq " + msgSeq);
} }
} }
catch (Exception e)
Message message = messageMap.get(uid);
if (message == null)
{ {
Log.e(K9.LOG_TAG, "Unable to store uid '" + uid + "' for msgSeq " + msgSeq); if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Do not have message in messageMap for UID " + uid + " for " + getLogId());
handleUntaggedResponse(response);
continue;
}
if (listener != null)
{
listener.messageStarted(uid, messageNumber++, messageMap.size());
}
ImapMessage imapMessage = (ImapMessage) message;
Object literal = handleFetchResponse(imapMessage, fetchList);
if (literal != null)
{
if (literal instanceof String)
{
String bodyString = (String)literal;
InputStream bodyStream = new ByteArrayInputStream(bodyString.getBytes());
imapMessage.parse(bodyStream);
}
else if (literal instanceof Integer)
{
// All the work was done in FetchBodyCallback.foundLiteral()
}
else
{
// This shouldn't happen
throw new MessagingException("Got FETCH response with bogus parameters");
}
}
if (listener != null)
{
listener.messageFinished(message, messageNumber, messageMap.size());
} }
} }
else
Message message = messageMap.get(uid);
if (message == null)
{ {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Do not have message in messageMap for UID " + uid + " for " + getLogId());
handleUntaggedResponse(response); handleUntaggedResponse(response);
continue;
}
if (listener != null)
{
listener.messageStarted(uid, messageNumber++, messageMap.size());
} }
ImapMessage imapMessage = (ImapMessage) message; while (response.more());
Object literal = handleFetchResponse(imapMessage, fetchList);
if (literal != null)
{
if (literal instanceof String)
{
String bodyString = (String)literal;
InputStream bodyStream = new ByteArrayInputStream(bodyString.getBytes());
imapMessage.parse(bodyStream);
}
else if (literal instanceof Integer)
{
// All the work was done in FetchBodyCallback.foundLiteral()
}
else
{
// This shouldn't happen
throw new MessagingException("Got FETCH response with bogus parameters");
}
}
if (listener != null)
{
listener.messageFinished(message, messageNumber, messageMap.size());
}
} }
else while (response.mTag == null);
{ }
handleUntaggedResponse(response); catch (IOException ioe)
} {
throw ioExceptionHandler(mConnection, ioe);
while (response.more());
} }
while (response.mTag == null);
}
catch (IOException ioe)
{
throw ioExceptionHandler(mConnection, ioe);
} }
} }