package com.fsck.k9.mail.store; import android.app.Application; import android.content.ContentValues; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.net.Uri; import android.util.Log; import com.fsck.k9.Account; import com.fsck.k9.K9; import com.fsck.k9.Preferences; import com.fsck.k9.controller.MessageRemovalListener; import com.fsck.k9.controller.MessageRetrievalListener; import com.fsck.k9.helper.Regex; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.*; import com.fsck.k9.mail.Message.RecipientType; import com.fsck.k9.mail.filter.Base64OutputStream; import com.fsck.k9.mail.internet.*; import com.fsck.k9.provider.AttachmentProvider; import org.apache.commons.io.IOUtils; import java.io.*; import java.net.URI; import java.net.URLEncoder; import java.util.*; import java.util.regex.Matcher; /** *
* Implements a SQLite database backed local store for Messages. **/ public class LocalStore extends Store implements Serializable { private static final int DB_VERSION = 35; private static final Flag[] PERMANENT_FLAGS = { Flag.DELETED, Flag.X_DESTROYED, Flag.SEEN, Flag.FLAGGED }; private String mPath; private SQLiteDatabase mDb; private File mAttachmentsDir; private Application mApplication; private String uUid = null; private static Set
"; } else { return ""; } } private String htmlifyMessageFooter() { if (K9.messageViewFixedWidthFont()) { return ""; } else { return ""; } } @Override public boolean isInTopGroup() { return inTopGroup; } public void setInTopGroup(boolean inTopGroup) { this.inTopGroup = inTopGroup; } } public class LocalTextBody extends TextBody { private String mBodyForDisplay; public LocalTextBody(String body) { super(body); } public LocalTextBody(String body, String bodyForDisplay) throws MessagingException { super(body); this.mBodyForDisplay = bodyForDisplay; } public String getBodyForDisplay() { return mBodyForDisplay; } public void setBodyForDisplay(String mBodyForDisplay) { this.mBodyForDisplay = mBodyForDisplay; } }//LocalTextBody public class LocalMessage extends MimeMessage { private long mId; private int mAttachmentCount; private String mSubject; private String mPreview = ""; private boolean mHeadersLoaded = false; private boolean mMessageDirty = false; public LocalMessage() { } LocalMessage(String uid, Folder folder) throws MessagingException { this.mUid = uid; this.mFolder = folder; } private void populateFromGetMessageCursor(Cursor cursor) throws MessagingException { this.setSubject(cursor.getString(0) == null ? "" : cursor.getString(0)); Address[] from = Address.unpack(cursor.getString(1)); if (from.length > 0) { this.setFrom(from[0]); } this.setInternalSentDate(new Date(cursor.getLong(2))); this.setUid(cursor.getString(3)); String flagList = cursor.getString(4); if (flagList != null && flagList.length() > 0) { String[] flags = flagList.split(","); for (String flag : flags) { try { this.setFlagInternal(Flag.valueOf(flag), true); } catch (Exception e) { if ("X_BAD_FLAG".equals(flag) == false) { Log.w(K9.LOG_TAG, "Unable to parse flag " + flag); } } } } this.mId = cursor.getLong(5); this.setRecipients(RecipientType.TO, Address.unpack(cursor.getString(6))); this.setRecipients(RecipientType.CC, Address.unpack(cursor.getString(7))); this.setRecipients(RecipientType.BCC, Address.unpack(cursor.getString(8))); this.setReplyTo(Address.unpack(cursor.getString(9))); this.mAttachmentCount = cursor.getInt(10); this.setInternalDate(new Date(cursor.getLong(11))); this.setMessageId(cursor.getString(12)); mPreview = (cursor.getString(14) == null ? "" : cursor.getString(14)); if (this.mFolder == null) { LocalFolder f = new LocalFolder(cursor.getInt(13)); f.open(LocalFolder.OpenMode.READ_WRITE); this.mFolder = f; } } /* Custom version of writeTo that updates the MIME message based on localMessage * changes. */ @Override public void writeTo(OutputStream out) throws IOException, MessagingException { if (mMessageDirty) buildMimeRepresentation(); super.writeTo(out); } private void buildMimeRepresentation() throws MessagingException { if (!mMessageDirty) { return; } super.setSubject(mSubject); if (this.mFrom != null && this.mFrom.length > 0) { super.setFrom(this.mFrom[0]); } super.setReplyTo(mReplyTo); super.setSentDate(this.getSentDate()); super.setRecipients(RecipientType.TO, mTo); super.setRecipients(RecipientType.CC, mCc); super.setRecipients(RecipientType.BCC, mBcc); if (mMessageId != null) super.setMessageId(mMessageId); mMessageDirty = false; return; } public String getPreview() { return mPreview; } @Override public String getSubject() throws MessagingException { return mSubject; } @Override public void setSubject(String subject) throws MessagingException { mSubject = subject; mMessageDirty = true; } @Override public void setMessageId(String messageId) { mMessageId = messageId; mMessageDirty = true; } public int getAttachmentCount() { return mAttachmentCount; } @Override public void setFrom(Address from) throws MessagingException { this.mFrom = new Address[] { from }; mMessageDirty = true; } @Override public void setReplyTo(Address[] replyTo) throws MessagingException { if (replyTo == null || replyTo.length == 0) { mReplyTo = null; } else { mReplyTo = replyTo; } mMessageDirty = true; } /* * For performance reasons, we add headers instead of setting them (see super implementation) * which removes (expensive) them before adding them */ @Override public void setRecipients(RecipientType type, Address[] addresses) throws MessagingException { if (type == RecipientType.TO) { if (addresses == null || addresses.length == 0) { this.mTo = null; } else { this.mTo = addresses; } } else if (type == RecipientType.CC) { if (addresses == null || addresses.length == 0) { this.mCc = null; } else { this.mCc = addresses; } } else if (type == RecipientType.BCC) { if (addresses == null || addresses.length == 0) { this.mBcc = null; } else { this.mBcc = addresses; } } else { throw new MessagingException("Unrecognized recipient type."); } mMessageDirty = true; } public void setFlagInternal(Flag flag, boolean set) throws MessagingException { super.setFlag(flag, set); } public long getId() { return mId; } @Override public void setFlag(Flag flag, boolean set) throws MessagingException { if (flag == Flag.DELETED && set) { /* * If a message is being marked as deleted we want to clear out it's content * and attachments as well. Delete will not actually remove the row since we need * to retain the uid for synchronization purposes. */ /* * Delete all of the messages' content to save space. */ ((LocalFolder) mFolder).deleteAttachments(getUid()); mDb.execSQL( "UPDATE messages SET " + "deleted = 1," + "subject = NULL, " + "sender_list = NULL, " + "date = NULL, " + "to_list = NULL, " + "cc_list = NULL, " + "bcc_list = NULL, " + "preview = NULL, " + "html_content = NULL, " + "text_content = NULL, " + "reply_to_list = NULL " + "WHERE id = ?", new Object[] { mId }); /* * Delete all of the messages' attachments to save space. */ mDb.execSQL("DELETE FROM attachments WHERE message_id = ?", new Object[] { mId }); ((LocalFolder)mFolder).deleteHeaders(mId); } else if (flag == Flag.X_DESTROYED && set) { ((LocalFolder) mFolder).deleteAttachments(getUid()); mDb.execSQL("DELETE FROM messages WHERE id = ?", new Object[] { mId }); ((LocalFolder)mFolder).deleteHeaders(mId); } /* * Update the unread count on the folder. */ try { if (flag == Flag.DELETED || flag == Flag.X_DESTROYED || (flag == Flag.SEEN && !isSet(Flag.DELETED))) { LocalFolder folder = (LocalFolder)mFolder; if (set && !isSet(Flag.SEEN)) { folder.setUnreadMessageCount(folder.getUnreadMessageCount() - 1); } else if (!set && isSet(Flag.SEEN)) { folder.setUnreadMessageCount(folder.getUnreadMessageCount() + 1); } } if ((flag == Flag.DELETED || flag == Flag.X_DESTROYED) && isSet(Flag.FLAGGED)) { LocalFolder folder = (LocalFolder)mFolder; if (set) { folder.setFlaggedMessageCount(folder.getFlaggedMessageCount() - 1); } else { folder.setFlaggedMessageCount(folder.getFlaggedMessageCount() + 1); } } if (flag == Flag.FLAGGED && !isSet(Flag.DELETED)) { LocalFolder folder = (LocalFolder)mFolder; if (set) { folder.setFlaggedMessageCount(folder.getFlaggedMessageCount() + 1); } else { folder.setFlaggedMessageCount(folder.getFlaggedMessageCount() - 1); } } } catch (MessagingException me) { Log.e(K9.LOG_TAG, "Unable to update LocalStore unread message count", me); throw new RuntimeException(me); } super.setFlag(flag, set); /* * Set the flags on the message. */ mDb.execSQL("UPDATE messages " + "SET flags = ? " + " WHERE id = ?", new Object[] { Utility.combine(getFlags(), ',').toUpperCase(), mId }); } private void loadHeaders() { ArrayListmessages = new ArrayList (); messages.add(this); mHeadersLoaded = true; // set true before calling populate headers to stop recursion ((LocalFolder) mFolder).populateHeaders(messages); } @Override public void addHeader(String name, String value) { if (!mHeadersLoaded) loadHeaders(); super.addHeader(name, value); } @Override public void setHeader(String name, String value) { if (!mHeadersLoaded) loadHeaders(); super.setHeader(name, value); } @Override public String[] getHeader(String name) { if (!mHeadersLoaded) loadHeaders(); return super.getHeader(name); } @Override public void removeHeader(String name) { if (!mHeadersLoaded) loadHeaders(); super.removeHeader(name); } @Override public Set getHeaderNames() { if (!mHeadersLoaded) loadHeaders(); return super.getHeaderNames(); } } public class LocalAttachmentBodyPart extends MimeBodyPart { private long mAttachmentId = -1; public LocalAttachmentBodyPart(Body body, long attachmentId) throws MessagingException { super(body); mAttachmentId = attachmentId; } /** * Returns the local attachment id of this body, or -1 if it is not stored. * @return */ public long getAttachmentId() { return mAttachmentId; } public void setAttachmentId(long attachmentId) { mAttachmentId = attachmentId; } @Override public String toString() { return "" + mAttachmentId; } } public static class LocalAttachmentBody implements Body { private Application mApplication; private Uri mUri; public LocalAttachmentBody(Uri uri, Application application) { mApplication = application; mUri = uri; } public InputStream getInputStream() throws MessagingException { try { return mApplication.getContentResolver().openInputStream(mUri); } catch (FileNotFoundException fnfe) { /* * Since it's completely normal for us to try to serve up attachments that * have been blown away, we just return an empty stream. */ return new ByteArrayInputStream(new byte[0]); } } public void writeTo(OutputStream out) throws IOException, MessagingException { InputStream in = getInputStream(); Base64OutputStream base64Out = new Base64OutputStream(out); IOUtils.copy(in, base64Out); base64Out.close(); } public Uri getContentUri() { return mUri; } } }