Finish loading large IMAP/POP messages
* Handle messages >25k * When structure is available (e.g. IMAP) pull in the entire body and the list of attachments * When structure is not available (e.g. POP) pull in a large chunk of the body to try and capture the message body at least. * Implement loadAttachment for IMAP/POP to demand download large items * Tested with IMAP & POP messages INCOMPLETE (file bugs): * implement logic for the old loadMessageForView calls that comes from MessageView (when you open a message that's partially-loaded) * Resolve handling of mimetype when attachment info is read (currently we're assuming base64 in a couple of places) * delete account => delete attachments * delete attachment => delete file * create account => clear existing attachments for acct id
This commit is contained in:
parent
6b2d23ad80
commit
0d10783635
|
@ -458,11 +458,12 @@ public class Controller {
|
|||
*
|
||||
* @param attachmentId the attachment to load
|
||||
* @param messageId the owner message
|
||||
* @param mailboxId the owner mailbox
|
||||
* @param accountId the owner account
|
||||
* @param callback the Controller callback by which results will be reported
|
||||
*/
|
||||
public void loadAttachment(long attachmentId, long messageId, long accountId,
|
||||
final Result callback) {
|
||||
public void loadAttachment(final long attachmentId, final long messageId, final long mailboxId,
|
||||
final long accountId, final Result callback) {
|
||||
|
||||
File saveToFile = AttachmentProvider.getAttachmentFilename(mContext,
|
||||
accountId, attachmentId);
|
||||
|
@ -495,6 +496,13 @@ public class Controller {
|
|||
}
|
||||
} else {
|
||||
// MessagingController implementation
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
mLegacyController.loadAttachment(accountId, messageId, mailboxId, attachmentId,
|
||||
mLegacyListener);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -651,14 +659,14 @@ public class Controller {
|
|||
@Override
|
||||
public void synchronizeMailboxFailed(EmailContent.Account account,
|
||||
EmailContent.Mailbox folder, Exception e) {
|
||||
MessagingException me;
|
||||
if (e instanceof MessagingException) {
|
||||
me = (MessagingException) e;
|
||||
} else {
|
||||
me = new MessagingException(e.toString());
|
||||
}
|
||||
synchronized (mListeners) {
|
||||
for (Result l : mListeners) {
|
||||
MessagingException me;
|
||||
if (e instanceof MessagingException) {
|
||||
me = (MessagingException) e;
|
||||
} else {
|
||||
me = new MessagingException(e.toString());
|
||||
}
|
||||
l.updateMailboxCallback(me, account.mId, folder.mId, 0, 0);
|
||||
}
|
||||
}
|
||||
|
@ -681,6 +689,36 @@ public class Controller {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentStarted(long accountId, long messageId, long attachmentId,
|
||||
boolean requiresDownload) {
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadAttachmentCallback(null, messageId, attachmentId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentFinished(long accountId, long messageId, long attachmentId) {
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadAttachmentCallback(null, messageId, attachmentId, 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentFailed(long accountId, long messageId, long attachmentId,
|
||||
String reason) {
|
||||
synchronized (mListeners) {
|
||||
for (Result listener : mListeners) {
|
||||
listener.loadAttachmentCallback(new MessagingException(reason),
|
||||
messageId, attachmentId, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,13 +16,13 @@
|
|||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.Part;
|
||||
import com.android.email.provider.EmailContent;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class GroupMessagingListener extends MessagingListener {
|
||||
/* The synchronization of the methods in this class
|
||||
|
@ -192,36 +192,33 @@ public class GroupMessagingListener extends MessagingListener {
|
|||
|
||||
@Override
|
||||
synchronized public void loadAttachmentStarted(
|
||||
EmailContent.Account account,
|
||||
Message message,
|
||||
Part part,
|
||||
Object tag,
|
||||
long accountId,
|
||||
long messageId,
|
||||
long attachmentId,
|
||||
boolean requiresDownload) {
|
||||
for (MessagingListener l : mListeners) {
|
||||
l.loadAttachmentStarted(account, message, part, tag, requiresDownload);
|
||||
l.loadAttachmentStarted(accountId, messageId, attachmentId, requiresDownload);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void loadAttachmentFinished(
|
||||
EmailContent.Account account,
|
||||
Message message,
|
||||
Part part,
|
||||
Object tag) {
|
||||
long accountId,
|
||||
long messageId,
|
||||
long attachmentId) {
|
||||
for (MessagingListener l : mListeners) {
|
||||
l.loadAttachmentFinished(account, message, part, tag);
|
||||
l.loadAttachmentFinished(accountId, messageId, attachmentId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
synchronized public void loadAttachmentFailed(
|
||||
EmailContent.Account account,
|
||||
Message message,
|
||||
Part part,
|
||||
Object tag,
|
||||
long accountId,
|
||||
long messageId,
|
||||
long attachmentId,
|
||||
String reason) {
|
||||
for (MessagingListener l : mListeners) {
|
||||
l.loadAttachmentFailed(account, message, part, tag, reason);
|
||||
l.loadAttachmentFailed(accountId, messageId, attachmentId, reason);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -232,10 +232,22 @@ public class LegacyConversions {
|
|||
localAttachment.save(context);
|
||||
|
||||
// If an attachment body was actually provided, we need to write the file now
|
||||
// TODO this should be separated so it can be reused for attachment downloads
|
||||
saveAttachmentBody(context, part, localAttachment, localMessage.mAccountKey);
|
||||
|
||||
if (localMessage.mAttachments == null) {
|
||||
localMessage.mAttachments = new ArrayList<Attachment>();
|
||||
}
|
||||
localMessage.mAttachments.add(localAttachment);
|
||||
localMessage.mFlagAttachment = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the body part of a single attachment, to a file in the attachments directory.
|
||||
*/
|
||||
public static void saveAttachmentBody(Context context, Part part, Attachment localAttachment,
|
||||
long accountId) throws MessagingException, IOException {
|
||||
if (part.getBody() != null) {
|
||||
long attachmentId = localAttachment.mId;
|
||||
long accountId = localMessage.mAccountKey;
|
||||
|
||||
InputStream in = part.getBody().getInputStream();
|
||||
|
||||
|
@ -265,11 +277,6 @@ public class LegacyConversions {
|
|||
Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, attachmentId);
|
||||
context.getContentResolver().update(uri, cv, null, null);
|
||||
}
|
||||
|
||||
if (localMessage.mAttachments == null) {
|
||||
localMessage.mAttachments = new ArrayList<Attachment>();
|
||||
}
|
||||
localMessage.mAttachments.add(localAttachment);
|
||||
localMessage.mFlagAttachment = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.mail.BodyPart;
|
||||
import com.android.email.mail.FetchProfile;
|
||||
import com.android.email.mail.Flag;
|
||||
import com.android.email.mail.Folder;
|
||||
|
@ -28,17 +29,24 @@ 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.MimeBodyPart;
|
||||
import com.android.email.mail.internet.MimeHeader;
|
||||
import com.android.email.mail.internet.MimeMultipart;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.mail.store.LocalStore;
|
||||
import com.android.email.mail.store.LocalStore.LocalFolder;
|
||||
import com.android.email.mail.store.LocalStore.LocalMessage;
|
||||
import com.android.email.mail.store.LocalStore.PendingCommand;
|
||||
import com.android.email.provider.AttachmentProvider;
|
||||
import com.android.email.provider.EmailContent;
|
||||
import com.android.email.provider.EmailContent.Attachment;
|
||||
import com.android.email.provider.EmailContent.AttachmentColumns;
|
||||
import com.android.email.provider.EmailContent.Mailbox;
|
||||
import com.android.email.provider.EmailContent.MailboxColumns;
|
||||
import com.android.email.provider.EmailContent.MessageColumns;
|
||||
import com.android.email.provider.EmailContent.SyncColumns;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
|
@ -47,6 +55,7 @@ import android.net.Uri;
|
|||
import android.os.Process;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
|
@ -95,6 +104,17 @@ public class MessagingController implements Runnable {
|
|||
private static final String PENDING_COMMAND_APPEND =
|
||||
"com.android.email.MessagingController.append";
|
||||
|
||||
/**
|
||||
* Projections & CVs used by pruneCachedAttachments
|
||||
*/
|
||||
private static String[] PRUNE_ATTACHMENT_PROJECTION = new String[] {
|
||||
AttachmentColumns.LOCATION
|
||||
};
|
||||
private static ContentValues PRUNE_ATTACHMENT_CV = new ContentValues();
|
||||
static {
|
||||
PRUNE_ATTACHMENT_CV.putNull(AttachmentColumns.CONTENT_URI);
|
||||
}
|
||||
|
||||
private static MessagingController inst = null;
|
||||
private BlockingQueue<Command> mCommands = new LinkedBlockingQueue<Command>();
|
||||
private Thread mThread;
|
||||
|
@ -869,93 +889,55 @@ public class MessagingController implements Runnable {
|
|||
remoteFolder.fetch(smallMessages.toArray(new Message[smallMessages.size()]), fp,
|
||||
new MessageRetrievalListener() {
|
||||
public void messageFinished(Message message, int number, int ofTotal) {
|
||||
try {
|
||||
EmailContent.Message localMessage = null;
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = mContext.getContentResolver().query(
|
||||
EmailContent.Message.CONTENT_URI,
|
||||
EmailContent.Message.CONTENT_PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
||||
" AND " + MessageColumns.MAILBOX_KEY + "=?" +
|
||||
" AND " + SyncColumns.SERVER_ID + "=?",
|
||||
new String[] {
|
||||
String.valueOf(account.mId),
|
||||
String.valueOf(folder.mId),
|
||||
String.valueOf(message.getUid())
|
||||
},
|
||||
null);
|
||||
if (c.moveToNext()) {
|
||||
localMessage = EmailContent.getContent(
|
||||
c, EmailContent.Message.class);
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
if (localMessage == null) {
|
||||
Log.d(Email.LOG_TAG, "Could not retrieve message from db, UUID="
|
||||
+ message.getUid());
|
||||
return;
|
||||
}
|
||||
|
||||
EmailContent.Body body = EmailContent.Body.restoreBodyWithId(
|
||||
mContext, localMessage.mId);
|
||||
if (body == null) {
|
||||
body = new EmailContent.Body();
|
||||
}
|
||||
try {
|
||||
// Copy the fields that are available into the message object
|
||||
LegacyConversions.updateMessageFields(localMessage, message,
|
||||
account.mId, folder.mId);
|
||||
|
||||
// Now process body parts & attachments
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
|
||||
LegacyConversions.updateBodyFields(body, localMessage, viewables);
|
||||
|
||||
// Commit the message & body to the local store immediately
|
||||
saveOrUpdate(localMessage);
|
||||
saveOrUpdate(body);
|
||||
|
||||
// process (and save) attachments
|
||||
LegacyConversions.updateAttachments(mContext, localMessage,
|
||||
attachments);
|
||||
|
||||
// One last update of message with two updated flags
|
||||
localMessage.mFlagLoaded = EmailContent.Message.LOADED;
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT,
|
||||
localMessage.mFlagAttachment);
|
||||
cv.put(EmailContent.MessageColumns.FLAG_LOADED,
|
||||
localMessage.mFlagLoaded);
|
||||
Uri uri = ContentUris.withAppendedId(
|
||||
EmailContent.Message.CONTENT_URI, localMessage.mId);
|
||||
mContext.getContentResolver().update(uri, cv, null, null);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Email.LOG_TAG,
|
||||
"Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
Log.e(Email.LOG_TAG,
|
||||
"Error while storing downloaded message." + rte.toString());
|
||||
} catch (IOException ioe) {
|
||||
Log.e(Email.LOG_TAG,
|
||||
"Error while storing attachment." + ioe.toString());
|
||||
}
|
||||
// Store the updated message locally and mark it fully loaded
|
||||
copyOneMessageToProvider(message, account, folder,
|
||||
EmailContent.Message.LOADED);
|
||||
}
|
||||
|
||||
public void messageStarted(String uid, int number, int ofTotal) {
|
||||
}
|
||||
});
|
||||
|
||||
// 14. Download large messages
|
||||
// 14. Download large messages. We ask the server to give us the message structure,
|
||||
// but not all of the attachments.
|
||||
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) {
|
||||
// POP doesn't support STRUCTURE mode, so we'll just do a partial download
|
||||
// (hopefully enough to see some/all of the body) and mark the message for
|
||||
// further download.
|
||||
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 partially-loaded message and mark it partially loaded
|
||||
copyOneMessageToProvider(message, account, folder,
|
||||
EmailContent.Message.PARTIALLY_LOADED);
|
||||
} 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<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
// Download the viewables immediately
|
||||
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 and mark it fully loaded
|
||||
copyOneMessageToProvider(message, account, folder, EmailContent.Message.LOADED);
|
||||
}
|
||||
}
|
||||
|
||||
// 15. Clean up and report results
|
||||
|
||||
|
@ -964,46 +946,6 @@ public class MessagingController implements Runnable {
|
|||
|
||||
// Original sync code. Using for reference, will delete when done.
|
||||
if (false) {
|
||||
/*
|
||||
* 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
|
||||
// synchronized (mListeners) {
|
||||
// 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.
|
||||
*/
|
||||
|
@ -1098,6 +1040,94 @@ public class MessagingController implements Runnable {
|
|||
|
||||
return new StoreSynchronizer.SyncResults(remoteMessageCount, newMessages.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy one downloaded message (which may have partially-loaded sections)
|
||||
* into a provider message
|
||||
*
|
||||
* @param message the remote message we've just downloaded
|
||||
* @param account the account it will be stored into
|
||||
* @param folder the mailbox it will be stored into
|
||||
* @param loadStatus when complete, the message will be marked with this status (e.g.
|
||||
* EmailContent.Message.LOADED)
|
||||
*/
|
||||
private void copyOneMessageToProvider(Message message, EmailContent.Account account,
|
||||
EmailContent.Mailbox folder, int loadStatus) {
|
||||
try {
|
||||
EmailContent.Message localMessage = null;
|
||||
Cursor c = null;
|
||||
try {
|
||||
c = mContext.getContentResolver().query(
|
||||
EmailContent.Message.CONTENT_URI,
|
||||
EmailContent.Message.CONTENT_PROJECTION,
|
||||
EmailContent.MessageColumns.ACCOUNT_KEY + "=?" +
|
||||
" AND " + MessageColumns.MAILBOX_KEY + "=?" +
|
||||
" AND " + SyncColumns.SERVER_ID + "=?",
|
||||
new String[] {
|
||||
String.valueOf(account.mId),
|
||||
String.valueOf(folder.mId),
|
||||
String.valueOf(message.getUid())
|
||||
},
|
||||
null);
|
||||
if (c.moveToNext()) {
|
||||
localMessage = EmailContent.getContent(c, EmailContent.Message.class);
|
||||
}
|
||||
} finally {
|
||||
if (c != null) {
|
||||
c.close();
|
||||
}
|
||||
}
|
||||
if (localMessage == null) {
|
||||
Log.d(Email.LOG_TAG, "Could not retrieve message from db, UUID="
|
||||
+ message.getUid());
|
||||
return;
|
||||
}
|
||||
|
||||
EmailContent.Body body = EmailContent.Body.restoreBodyWithMessageId(mContext,
|
||||
localMessage.mId);
|
||||
if (body == null) {
|
||||
body = new EmailContent.Body();
|
||||
}
|
||||
try {
|
||||
// Copy the fields that are available into the message object
|
||||
LegacyConversions.updateMessageFields(localMessage, message, account.mId,
|
||||
folder.mId);
|
||||
|
||||
// Now process body parts & attachments
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
|
||||
LegacyConversions.updateBodyFields(body, localMessage, viewables);
|
||||
|
||||
// Commit the message & body to the local store immediately
|
||||
saveOrUpdate(localMessage);
|
||||
saveOrUpdate(body);
|
||||
|
||||
// process (and save) attachments
|
||||
LegacyConversions.updateAttachments(mContext, localMessage,
|
||||
attachments);
|
||||
|
||||
// One last update of message with two updated flags
|
||||
localMessage.mFlagLoaded = loadStatus;
|
||||
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put(EmailContent.MessageColumns.FLAG_ATTACHMENT, localMessage.mFlagAttachment);
|
||||
cv.put(EmailContent.MessageColumns.FLAG_LOADED, localMessage.mFlagLoaded);
|
||||
Uri uri = ContentUris.withAppendedId(EmailContent.Message.CONTENT_URI,
|
||||
localMessage.mId);
|
||||
mContext.getContentResolver().update(uri, cv, null, null);
|
||||
|
||||
} catch (MessagingException me) {
|
||||
Log.e(Email.LOG_TAG, "Error while copying downloaded message." + me);
|
||||
}
|
||||
|
||||
} catch (RuntimeException rte) {
|
||||
Log.e(Email.LOG_TAG, "Error while storing downloaded message." + rte.toString());
|
||||
} catch (IOException ioe) {
|
||||
Log.e(Email.LOG_TAG, "Error while storing attachment." + ioe.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private void queuePendingCommand(EmailContent.Account account, PendingCommand command) {
|
||||
try {
|
||||
|
@ -1549,78 +1579,120 @@ public class MessagingController implements Runnable {
|
|||
}
|
||||
|
||||
/**
|
||||
* Attempts to load the attachment specified by part from the given account and message.
|
||||
* Attempts to load the attachment specified by id from the given account and message.
|
||||
* @param account
|
||||
* @param message
|
||||
* @param part
|
||||
* @param listener
|
||||
*/
|
||||
public void loadAttachment(
|
||||
final EmailContent.Account account,
|
||||
final Message message,
|
||||
final Part part,
|
||||
final Object tag,
|
||||
MessagingListener listener) {
|
||||
/*
|
||||
* Check if the attachment has already been downloaded. If it has there's no reason to
|
||||
* download it, so we just tell the listener that it's ready to go.
|
||||
*/
|
||||
try {
|
||||
if (part.getBody() != null) {
|
||||
mListeners.loadAttachmentStarted(account, message, part, tag, false);
|
||||
mListeners.loadAttachmentFinished(account, message, part, tag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (MessagingException me) {
|
||||
/*
|
||||
* If the header isn't there the attachment isn't downloaded yet, so just continue
|
||||
* on.
|
||||
*/
|
||||
}
|
||||
|
||||
mListeners.loadAttachmentStarted(account, message, part, tag, true);
|
||||
public void loadAttachment(final long accountId, final long messageId, final long mailboxId,
|
||||
final long attachmentId, MessagingListener listener) {
|
||||
mListeners.loadAttachmentStarted(accountId, messageId, attachmentId, true);
|
||||
|
||||
put("loadAttachment", listener, new Runnable() {
|
||||
public void run() {
|
||||
try {
|
||||
LocalStore localStore = (LocalStore) Store.getInstance(
|
||||
account.getLocalStoreUri(mContext), mContext, null);
|
||||
/*
|
||||
* We clear out any attachments already cached in the entire store and then
|
||||
* we update the passed in message to reflect that there are no cached
|
||||
* attachments. This is in support of limiting the account to having one
|
||||
* attachment downloaded at a time.
|
||||
*/
|
||||
localStore.pruneCachedAttachments();
|
||||
ArrayList<Part> viewables = new ArrayList<Part>();
|
||||
ArrayList<Part> attachments = new ArrayList<Part>();
|
||||
MimeUtility.collectParts(message, viewables, attachments);
|
||||
for (Part attachment : attachments) {
|
||||
attachment.setBody(null);
|
||||
}
|
||||
Store remoteStore = Store.getInstance(account.getStoreUri(mContext), mContext,
|
||||
localStore.getPersistentCallbacks());
|
||||
LocalFolder localFolder =
|
||||
(LocalFolder) localStore.getFolder(message.getFolder().getName());
|
||||
Folder remoteFolder = remoteStore.getFolder(message.getFolder().getName());
|
||||
remoteFolder.open(OpenMode.READ_WRITE, localFolder.getPersistentCallbacks());
|
||||
// 1. Pruning. Policy is to have one downloaded attachment at a time,
|
||||
// per account, to reduce disk storage pressure.
|
||||
pruneCachedAttachments(accountId);
|
||||
|
||||
// 2. Open the remote folder.
|
||||
// TODO all of these could be narrower projections
|
||||
EmailContent.Account account =
|
||||
EmailContent.Account.restoreAccountWithId(mContext, accountId);
|
||||
EmailContent.Mailbox mailbox =
|
||||
EmailContent.Mailbox.restoreMailboxWithId(mContext, mailboxId);
|
||||
EmailContent.Message message =
|
||||
EmailContent.Message.restoreMessageWithId(mContext, messageId);
|
||||
Attachment attachment =
|
||||
Attachment.restoreAttachmentWithId(mContext, attachmentId);
|
||||
|
||||
Store remoteStore =
|
||||
Store.getInstance(account.getStoreUri(mContext), mContext, null);
|
||||
Folder remoteFolder = remoteStore.getFolder(mailbox.mDisplayName);
|
||||
remoteFolder.open(OpenMode.READ_WRITE, null);
|
||||
|
||||
// 3. Generate a shell message in which to retrieve the attachment,
|
||||
// and a shell BodyPart for the attachment. Then glue them together.
|
||||
Message storeMessage = remoteFolder.createMessage(message.mServerId);
|
||||
BodyPart storePart = new MimeBodyPart();
|
||||
storePart.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA,
|
||||
attachment.mLocation);
|
||||
storePart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
String.format("%s;\n name=\"%s\"",
|
||||
attachment.mMimeType,
|
||||
attachment.mFileName));
|
||||
// TODO is this always true for attachments? I think we dropped the
|
||||
// true encoding along the way
|
||||
storePart.setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, "base64");
|
||||
|
||||
MimeMultipart multipart = new MimeMultipart();
|
||||
multipart.setSubType("mixed");
|
||||
multipart.addBodyPart(storePart);
|
||||
|
||||
storeMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "multipart/mixed");
|
||||
storeMessage.setBody(multipart);
|
||||
|
||||
// 4. Now ask for the attachment to be fetched
|
||||
FetchProfile fp = new FetchProfile();
|
||||
fp.add(part);
|
||||
remoteFolder.fetch(new Message[] { message }, fp, null);
|
||||
localFolder.updateMessage((LocalMessage)message);
|
||||
localFolder.close(false);
|
||||
mListeners.loadAttachmentFinished(account, message, part, tag);
|
||||
fp.add(storePart);
|
||||
remoteFolder.fetch(new Message[] { storeMessage }, fp, null);
|
||||
|
||||
// 5. Save the downloaded file and update the attachment as necessary
|
||||
LegacyConversions.saveAttachmentBody(mContext, storePart, attachment,
|
||||
accountId);
|
||||
|
||||
// 6. Report success
|
||||
mListeners.loadAttachmentFinished(accountId, messageId, attachmentId);
|
||||
}
|
||||
catch (MessagingException me) {
|
||||
if (Email.LOGD) {
|
||||
Log.v(Email.LOG_TAG, "", me);
|
||||
if (Email.LOGD) Log.v(Email.LOG_TAG, "", me);
|
||||
mListeners.loadAttachmentFailed(accountId, messageId, attachmentId,
|
||||
me.getMessage());
|
||||
} catch (IOException ioe) {
|
||||
Log.e(Email.LOG_TAG, "Error while storing attachment." + ioe.toString());
|
||||
}
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
* Erase all stored attachments for a given account. Rules:
|
||||
* 1. All files in attachment directory are up for deletion
|
||||
* 2. If filename does not match an known attachment id, it's deleted
|
||||
* 3. If the attachment has location data (implying that it's reloadable), it's deleted
|
||||
*/
|
||||
/* package */ void pruneCachedAttachments(long accountId) {
|
||||
ContentResolver resolver = mContext.getContentResolver();
|
||||
File cacheDir = AttachmentProvider.getAttachmentDirectory(mContext, accountId);
|
||||
for (File file : cacheDir.listFiles()) {
|
||||
if (file.exists()) {
|
||||
long id;
|
||||
try {
|
||||
// the name of the file == the attachment id
|
||||
id = Long.valueOf(file.getName());
|
||||
Uri uri = ContentUris.withAppendedId(Attachment.CONTENT_URI, id);
|
||||
Cursor c = resolver.query(uri, PRUNE_ATTACHMENT_PROJECTION, null, null, null);
|
||||
try {
|
||||
if (c.moveToNext()) {
|
||||
// if there is no way to reload the attachment, don't delete it
|
||||
if (c.getString(0) == null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
c.close();
|
||||
}
|
||||
mListeners.loadAttachmentFailed(account, message, part, tag, me.getMessage());
|
||||
// Clear the content URI field since we're losing the attachment
|
||||
resolver.update(uri, PRUNE_ATTACHMENT_CV, null, null);
|
||||
} catch (NumberFormatException nfe) {
|
||||
// ignore filename != number error, and just delete it anyway
|
||||
}
|
||||
// This file can be safely deleted
|
||||
if (!file.delete()) {
|
||||
file.deleteOnExit();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,9 +16,7 @@
|
|||
|
||||
package com.android.email;
|
||||
|
||||
import com.android.email.mail.Folder;
|
||||
import com.android.email.mail.Message;
|
||||
import com.android.email.mail.Part;
|
||||
import com.android.email.provider.EmailContent;
|
||||
|
||||
import android.content.Context;
|
||||
|
@ -95,29 +93,23 @@ public class MessagingListener {
|
|||
}
|
||||
|
||||
public void loadAttachmentStarted(
|
||||
EmailContent.Account account,
|
||||
Message message,
|
||||
Part part,
|
||||
Object tag,
|
||||
boolean requiresDownload)
|
||||
{
|
||||
long accountId,
|
||||
long messageId,
|
||||
long attachmentId,
|
||||
boolean requiresDownload) {
|
||||
}
|
||||
|
||||
public void loadAttachmentFinished(
|
||||
EmailContent.Account account,
|
||||
Message message,
|
||||
Part part,
|
||||
Object tag)
|
||||
{
|
||||
long accountId,
|
||||
long messageId,
|
||||
long attachmentId) {
|
||||
}
|
||||
|
||||
public void loadAttachmentFailed(
|
||||
EmailContent.Account account,
|
||||
Message message,
|
||||
Part part,
|
||||
Object tag,
|
||||
String reason)
|
||||
{
|
||||
long accountId,
|
||||
long messageId,
|
||||
long attachmentId,
|
||||
String reason) {
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -24,7 +24,6 @@ import com.android.email.R;
|
|||
import com.android.email.Utility;
|
||||
import com.android.email.mail.Address;
|
||||
import com.android.email.mail.MessagingException;
|
||||
import com.android.email.mail.Part;
|
||||
import com.android.email.mail.internet.EmailHtmlUtil;
|
||||
import com.android.email.mail.internet.MimeUtility;
|
||||
import com.android.email.provider.AttachmentProvider;
|
||||
|
@ -669,7 +668,7 @@ public class MessageView extends Activity
|
|||
mLoadAttachmentName = attachment.name;
|
||||
|
||||
Controller.getInstance(getApplication()).loadAttachment(attachment.attachmentId,
|
||||
mMessageId, mAccountId, mControllerCallback);
|
||||
mMessageId, mMessage.mMailboxKey, mAccountId, mControllerCallback);
|
||||
}
|
||||
|
||||
private void onViewAttachment(AttachmentInfo attachment) {
|
||||
|
@ -678,7 +677,7 @@ public class MessageView extends Activity
|
|||
mLoadAttachmentName = attachment.name;
|
||||
|
||||
Controller.getInstance(getApplication()).loadAttachment(attachment.attachmentId,
|
||||
mMessageId, mAccountId, mControllerCallback);
|
||||
mMessageId, mMessage.mMailboxKey, mAccountId, mControllerCallback);
|
||||
}
|
||||
|
||||
private void onShowPictures() {
|
||||
|
@ -1389,29 +1388,29 @@ public class MessageView extends Activity
|
|||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadAttachmentStarted(Account account, com.android.email.mail.Message message,
|
||||
Part part, Object tag, boolean requiresDownload) {
|
||||
mHandler.setAttachmentsEnabled(false);
|
||||
Object[] params = (Object[]) tag;
|
||||
// mHandler.progress(true, ((AttachmentInfo) params[1]).name);
|
||||
if (requiresDownload) {
|
||||
mHandler.fetchingAttachment();
|
||||
}
|
||||
}
|
||||
// @Override
|
||||
// public void loadAttachmentStarted(Account account, com.android.email.mail.Message message,
|
||||
// Part part, Object tag, boolean requiresDownload) {
|
||||
// mHandler.setAttachmentsEnabled(false);
|
||||
// Object[] params = (Object[]) tag;
|
||||
//// mHandler.progress(true, ((AttachmentInfo) params[1]).name);
|
||||
// if (requiresDownload) {
|
||||
// mHandler.fetchingAttachment();
|
||||
// }
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void loadAttachmentFinished(Account account, com.android.email.mail.Message message,
|
||||
Part part, Object tag) {
|
||||
mHandler.setAttachmentsEnabled(true);
|
||||
// @Override
|
||||
// public void loadAttachmentFinished(Account account, com.android.email.mail.Message message,
|
||||
// Part part, Object tag) {
|
||||
// mHandler.setAttachmentsEnabled(true);
|
||||
// mHandler.progress(false, null);
|
||||
// updateAttachmentThumbnail(part);
|
||||
|
||||
Object[] params = (Object[]) tag;
|
||||
boolean download = (Boolean) params[0];
|
||||
AttachmentInfo attachment = (AttachmentInfo) params[1];
|
||||
|
||||
if (download) {
|
||||
//
|
||||
// Object[] params = (Object[]) tag;
|
||||
// boolean download = (Boolean) params[0];
|
||||
// AttachmentInfo attachment = (AttachmentInfo) params[1];
|
||||
//
|
||||
// if (download) {
|
||||
// try {
|
||||
// File file = createUniqueFile(Environment.getExternalStorageDirectory(),
|
||||
// attachment.name);
|
||||
|
@ -1430,8 +1429,8 @@ public class MessageView extends Activity
|
|||
// catch (IOException ioe) {
|
||||
// mHandler.attachmentNotSaved();
|
||||
// }
|
||||
}
|
||||
else {
|
||||
// }
|
||||
// else {
|
||||
// try {
|
||||
// Uri uri = AttachmentProvider.resolveAttachmentIdToContentUri(
|
||||
// getContentResolver(), AttachmentProvider.getAttachmentUri(
|
||||
|
@ -1445,16 +1444,16 @@ public class MessageView extends Activity
|
|||
// // TODO: Add a proper warning message (and lots of upstream cleanup to prevent
|
||||
// // it from happening) in the next release.
|
||||
// }
|
||||
}
|
||||
}
|
||||
// }
|
||||
// }
|
||||
|
||||
@Override
|
||||
public void loadAttachmentFailed(Account account, com.android.email.mail.Message message,
|
||||
Part part, Object tag, String reason) {
|
||||
mHandler.setAttachmentsEnabled(true);
|
||||
// mHandler.progress(false, null);
|
||||
mHandler.networkError();
|
||||
}
|
||||
// @Override
|
||||
// public void loadAttachmentFailed(Account account, com.android.email.mail.Message message,
|
||||
// Part part, Object tag, String reason) {
|
||||
// mHandler.setAttachmentsEnabled(true);
|
||||
//// mHandler.progress(false, null);
|
||||
// mHandler.networkError();
|
||||
// }
|
||||
|
||||
/**
|
||||
* Safely load a URL for mMessageContentView, or drop it if the view is gone
|
||||
|
|
|
@ -167,6 +167,11 @@ public abstract class Folder {
|
|||
// Do nothing - return immediately
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an empty message of the appropriate type for the Folder.
|
||||
*/
|
||||
public abstract Message createMessage(String uid) throws MessagingException;
|
||||
|
||||
/**
|
||||
* Callback interface by which a Folder can read and write persistent data.
|
||||
* TODO This needs to be made more generic & flexible
|
||||
|
|
|
@ -164,5 +164,9 @@ public class ExchangeFolderExample extends Folder {
|
|||
// TODO Implement this function
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Message createMessage(String uid) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -447,7 +447,11 @@ public class ExchangeStore extends Store {
|
|||
// TODO Implement this function
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Message createMessage(String uid) {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1090,6 +1090,11 @@ public class ImapStore extends Store {
|
|||
}
|
||||
return super.equals(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(String uid) throws MessagingException {
|
||||
return new ImapMessage(uid, this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1582,6 +1582,11 @@ public class LocalStore extends Store implements PersistentDataCallbacks {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(String uid) throws MessagingException {
|
||||
return new LocalMessage(uid, this);
|
||||
}
|
||||
}
|
||||
|
||||
public class LocalMessage extends MimeMessage {
|
||||
|
|
|
@ -29,6 +29,7 @@ import com.android.email.mail.Store;
|
|||
import com.android.email.mail.Transport;
|
||||
import com.android.email.mail.Folder.OpenMode;
|
||||
import com.android.email.mail.internet.MimeMessage;
|
||||
import com.android.email.mail.store.ImapStore.ImapMessage;
|
||||
import com.android.email.mail.transport.LoggingInputStream;
|
||||
import com.android.email.mail.transport.MailTransport;
|
||||
|
||||
|
@ -899,6 +900,11 @@ public class Pop3Store extends Store {
|
|||
public boolean isOpen() {
|
||||
return mTransport.isOpen();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(String uid) throws MessagingException {
|
||||
return new Pop3Message(uid, this);
|
||||
}
|
||||
}
|
||||
|
||||
class Pop3Message extends MimeMessage {
|
||||
|
|
|
@ -142,4 +142,10 @@ public class MockFolder extends Folder {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Message createMessage(String uid) throws MessagingException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue