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

Merge pull request #3 from frommeyerc/merge-upstream

Extract inner classes from LocalStore
This commit is contained in:
cketti 2014-09-12 07:07:07 +02:00
commit bd839a995f
35 changed files with 4770 additions and 4609 deletions

View File

@ -31,9 +31,9 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.StatsColumns;
import com.fsck.k9.search.ConditionsTreeNode;

View File

@ -37,7 +37,7 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.provider.UnreadWidgetProvider;
import com.fsck.k9.security.LocalKeyStore;
import com.fsck.k9.service.BootReceiver;

View File

@ -56,7 +56,7 @@ import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.search.LocalSearch;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.Searchfield;

View File

@ -107,9 +107,9 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.internet.TextBodyBuilder;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
import com.fsck.k9.mail.store.LocalStore.TempFileBody;
import com.fsck.k9.mail.store.LocalStore.TempFileMessageBody;
import com.fsck.k9.mail.store.local.LocalAttachmentBody;
import com.fsck.k9.mail.store.local.TempFileBody;
import com.fsck.k9.mail.store.local.TempFileMessageBody;
import com.fsck.k9.view.MessageWebView;
import org.apache.james.mime4j.codec.EncoderUtil;

View File

@ -30,7 +30,7 @@ import android.widget.TextView;
* <li>{@link #actionUpgradeDatabases(Context, Intent)} will call {@link K9#areDatabasesUpToDate()}
* to check if we already know whether the databases have been upgraded.</li>
* <li>{@link K9#areDatabasesUpToDate()} will compare the last known database version stored in a
* {@link SharedPreferences} file to {@link com.fsck.k9.mail.store.LocalStore#DB_VERSION}. This
* {@link SharedPreferences} file to {@link com.fsck.k9.mail.store.local.LocalStore#DB_VERSION}. This
* is done as an optimization because it's faster than opening all of the accounts' databases
* one by one.</li>
* <li>If there was an error reading the cached database version or if it shows the databases need

View File

@ -41,7 +41,7 @@ import com.fsck.k9.activity.ManageIdentities;
import com.fsck.k9.crypto.Apg;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.service.MailService;

View File

@ -16,8 +16,8 @@ import com.fsck.k9.mail.Folder.FolderClass;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.service.MailService;
public class FolderSettings extends K9PreferenceActivity {

View File

@ -11,8 +11,8 @@ import android.support.v4.content.LocalBroadcastManager;
import com.fsck.k9.fragment.MessageListFragment;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.provider.EmailProvider;
/**

View File

@ -79,10 +79,10 @@ import com.fsck.k9.mail.Transport;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.local.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.UnavailableAccountException;
import com.fsck.k9.mail.store.UnavailableStorageException;

View File

@ -11,8 +11,8 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.PushReceiver;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.service.SleepService;
import java.util.List;

View File

@ -88,8 +88,8 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.SpecialColumns;

View File

@ -39,7 +39,7 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
import com.fsck.k9.view.MessageHeader;

View File

@ -12,9 +12,9 @@ import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.mail.store.ImapStore;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.WebDavStore;

File diff suppressed because it is too large Load Diff

View File

@ -376,24 +376,12 @@ public class LockableDatabase {
try {
final File databaseFile = prepareStorage(mStorageProviderId);
try {
if (StorageManager.InternalStorageProvider.ID.equals(mStorageProviderId)) {
// internal storage
mDb = application.openOrCreateDatabase(databaseFile.getName(), Context.MODE_PRIVATE, null);
} else {
// external storage
mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
}
doOpenOrCreateDb(application, databaseFile);
} catch (SQLiteException e) {
// try to gracefully handle DB corruption - see issue 2537
Log.w(K9.LOG_TAG, "Unable to open DB " + databaseFile + " - removing file and retrying", e);
databaseFile.delete();
if (StorageManager.InternalStorageProvider.ID.equals(mStorageProviderId)) {
// internal storage
mDb = application.openOrCreateDatabase(databaseFile.getName(), Context.MODE_PRIVATE, null);
} else {
// external storage
mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
}
doOpenOrCreateDb(application, databaseFile);
}
if (mDb.getVersion() != mSchemaDefinition.getVersion()) {
mSchemaDefinition.doDbUpgrade(mDb);
@ -403,6 +391,17 @@ public class LockableDatabase {
}
}
private void doOpenOrCreateDb(final Application application, final File databaseFile) {
if (StorageManager.InternalStorageProvider.ID.equals(mStorageProviderId)) {
// internal storage
mDb = application.openOrCreateDatabase(databaseFile.getName(), Context.MODE_PRIVATE,
null);
} else {
// external storage
mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
}
}
/**
* @param providerId
* Never <code>null</code>.
@ -412,10 +411,8 @@ public class LockableDatabase {
protected File prepareStorage(final String providerId) throws UnavailableStorageException {
final StorageManager storageManager = getStorageManager();
final File databaseFile;
final File databaseParentDir;
databaseFile = storageManager.getDatabase(uUid, providerId);
databaseParentDir = databaseFile.getParentFile();
final File databaseFile = storageManager.getDatabase(uUid, providerId);
final File databaseParentDir = databaseFile.getParentFile();
if (databaseParentDir.isFile()) {
// should be safe to unconditionally delete clashing file: user is not supposed to mess with our directory
databaseParentDir.delete();
@ -428,11 +425,8 @@ public class LockableDatabase {
Utility.touchFile(databaseParentDir, ".nomedia");
}
final File attachmentDir;
final File attachmentParentDir;
attachmentDir = storageManager
.getAttachmentDirectory(uUid, providerId);
attachmentParentDir = attachmentDir.getParentFile();
final File attachmentDir = storageManager.getAttachmentDirectory(uUid, providerId);
final File attachmentParentDir = attachmentDir.getParentFile();
if (!attachmentParentDir.exists()) {
attachmentParentDir.mkdirs();
Utility.touchFile(attachmentParentDir, ".nomedia");
@ -467,7 +461,8 @@ public class LockableDatabase {
try {
mDb.close();
} catch (Exception e) {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Exception caught in DB close: " + e.getMessage());
}
final StorageManager storageManager = getStorageManager();
try {
@ -482,6 +477,8 @@ public class LockableDatabase {
attachmentDirectory.delete();
}
} catch (Exception e) {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Exception caught in clearing attachments: " + e.getMessage());
}
try {
deleteDatabase(storageManager.getDatabase(uUid, mStorageProviderId));

View File

@ -0,0 +1,34 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeMessage;
public class AttachmentMessageBodyUtil {
public static void writeTo(BinaryAttachmentBody body, OutputStream out) throws IOException,
MessagingException {
InputStream in = body.getInputStream();
try {
if (MimeUtil.ENC_7BIT.equalsIgnoreCase(body.getEncoding())) {
/*
* If we knew the message was already 7bit clean, then it
* could be sent along without processing. But since we
* don't know, we recursively parse it.
*/
MimeMessage message = new MimeMessage(in, true);
message.setUsing7bitTransport();
message.writeTo(out);
} else {
IOUtils.copy(in, out);
}
} finally {
in.close();
}
}
}

View File

@ -0,0 +1,54 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.filter.Base64OutputStream;
public abstract class BinaryAttachmentBody implements Body {
protected String mEncoding;
@Override
public abstract InputStream getInputStream() throws MessagingException;
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
InputStream in = getInputStream();
try {
boolean closeStream = false;
if (MimeUtil.isBase64Encoding(mEncoding)) {
out = new Base64OutputStream(out);
closeStream = true;
} else if (MimeUtil.isQuotedPrintableEncoded(mEncoding)){
out = new QuotedPrintableOutputStream(out, false);
closeStream = true;
}
try {
IOUtils.copy(in, out);
} finally {
if (closeStream) {
out.close();
}
}
} finally {
in.close();
}
}
@Override
public void setEncoding(String encoding) throws MessagingException {
mEncoding = encoding;
}
public String getEncoding() {
return mEncoding;
}
}

View File

@ -0,0 +1,37 @@
package com.fsck.k9.mail.store.local;
import java.io.ByteArrayInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import android.app.Application;
import android.net.Uri;
import com.fsck.k9.mail.MessagingException;
public class LocalAttachmentBody extends BinaryAttachmentBody {
private Application mApplication;
private Uri mUri;
public LocalAttachmentBody(Uri uri, Application application) {
mApplication = application;
mUri = uri;
}
@Override
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(LocalStore.EMPTY_BYTE_ARRAY);
}
}
public Uri getContentUri() {
return mUri;
}
}

View File

@ -0,0 +1,31 @@
package com.fsck.k9.mail.store.local;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeBodyPart;
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;
}
}

View File

@ -0,0 +1,49 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.james.mime4j.util.MimeUtil;
import android.app.Application;
import android.net.Uri;
import com.fsck.k9.mail.CompositeBody;
import com.fsck.k9.mail.MessagingException;
/**
* A {@link LocalAttachmentBody} extension containing a message/rfc822 type body
*
*/
public class LocalAttachmentMessageBody extends LocalAttachmentBody implements CompositeBody {
public LocalAttachmentMessageBody(Uri uri, Application application) {
super(uri, application);
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
AttachmentMessageBodyUtil.writeTo(this, out);
}
@Override
public void setUsing7bitTransport() throws MessagingException {
/*
* There's nothing to recurse into here, so there's nothing to do.
* The enclosing BodyPart already called setEncoding(MimeUtil.ENC_7BIT). Once
* writeTo() is called, the file with the rfc822 body will be opened
* for reading and will then be recursed.
*/
}
@Override
public void setEncoding(String encoding) throws MessagingException {
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
throw new MessagingException(
"Incompatible content-transfer-encoding applied to a CompositeBody");
}
mEncoding = encoding;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,559 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.Set;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
import com.fsck.k9.K9;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
public class LocalMessage extends MimeMessage {
private final LocalStore localStore;
long mId;
private int mAttachmentCount;
private String mSubject;
private String mPreview = "";
private boolean mHeadersLoaded = false;
private boolean mMessageDirty = false;
private long mThreadId;
private long mRootId;
public LocalMessage(LocalStore localStore) {
this.localStore = localStore;
}
LocalMessage(LocalStore localStore, String uid, Folder folder) {
this.localStore = localStore;
this.mUid = uid;
this.mFolder = folder;
}
void populateFromGetMessageCursor(Cursor cursor)
throws MessagingException {
final String subject = cursor.getString(0);
this.setSubject(subject == null ? "" : subject);
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)) {
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));
final String preview = cursor.getString(14);
mPreview = (preview == null ? "" : preview);
if (this.mFolder == null) {
LocalFolder f = new LocalFolder(this.localStore, cursor.getInt(13));
f.open(LocalFolder.OPEN_MODE_RW);
this.mFolder = f;
}
mThreadId = (cursor.isNull(15)) ? -1 : cursor.getLong(15);
mRootId = (cursor.isNull(16)) ? -1 : cursor.getLong(16);
boolean deleted = (cursor.getInt(17) == 1);
boolean read = (cursor.getInt(18) == 1);
boolean flagged = (cursor.getInt(19) == 1);
boolean answered = (cursor.getInt(20) == 1);
boolean forwarded = (cursor.getInt(21) == 1);
setFlagInternal(Flag.DELETED, deleted);
setFlagInternal(Flag.SEEN, read);
setFlagInternal(Flag.FLAGGED, flagged);
setFlagInternal(Flag.ANSWERED, answered);
setFlagInternal(Flag.FORWARDED, forwarded);
}
/**
* Fetch the message text for display. This always returns an HTML-ified version of the
* message, even if it was originally a text-only message.
* @return HTML version of message for display purposes or null.
* @throws MessagingException
*/
public String getTextForDisplay() throws MessagingException {
String text = null; // First try and fetch an HTML part.
Part part = MimeUtility.findFirstPartByMimeType(this, "text/html");
if (part == null) {
// If that fails, try and get a text part.
part = MimeUtility.findFirstPartByMimeType(this, "text/plain");
if (part != null && part.getBody() instanceof LocalTextBody) {
text = ((LocalTextBody) part.getBody()).getBodyForDisplay();
}
} else {
// We successfully found an HTML part; do the necessary character set decoding.
text = MimeUtility.getTextFromPart(part);
}
return text;
}
/* 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);
}
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;
}
@Override
public String getPreview() {
return mPreview;
}
@Override
public String getSubject() {
return mSubject;
}
@Override
public void setSubject(String subject) throws MessagingException {
mSubject = subject;
mMessageDirty = true;
}
@Override
public void setMessageId(String messageId) {
mMessageId = messageId;
mMessageDirty = true;
}
@Override
public boolean hasAttachments() {
return (mAttachmentCount > 0);
}
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);
}
@Override
public long getId() {
return mId;
}
@Override
public void setFlag(final Flag flag, final boolean set) throws MessagingException {
try {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
try {
if (flag == Flag.DELETED && set) {
delete();
}
LocalMessage.super.setFlag(flag, set);
} catch (MessagingException e) {
throw new WrappedException(e);
}
/*
* Set the flags on the message.
*/
ContentValues cv = new ContentValues();
cv.put("flags", LocalMessage.this.localStore.serializeFlags(getFlags()));
cv.put("read", isSet(Flag.SEEN) ? 1 : 0);
cv.put("flagged", isSet(Flag.FLAGGED) ? 1 : 0);
cv.put("answered", isSet(Flag.ANSWERED) ? 1 : 0);
cv.put("forwarded", isSet(Flag.FORWARDED) ? 1 : 0);
db.update("messages", cv, "id = ?", new String[] { Long.toString(mId) });
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
this.localStore.notifyChange();
}
/*
* 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.
*/
private void delete() throws MessagingException
{
/*
* Delete all of the message's content to save space.
*/
try {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
UnavailableStorageException {
String[] idArg = new String[] { Long.toString(mId) };
ContentValues cv = new ContentValues();
cv.put("deleted", 1);
cv.put("empty", 1);
cv.putNull("subject");
cv.putNull("sender_list");
cv.putNull("date");
cv.putNull("to_list");
cv.putNull("cc_list");
cv.putNull("bcc_list");
cv.putNull("preview");
cv.putNull("html_content");
cv.putNull("text_content");
cv.putNull("reply_to_list");
db.update("messages", cv, "id = ?", idArg);
/*
* Delete all of the message's attachments to save space.
* We do this explicit deletion here because we're not deleting the record
* in messages, which means our ON DELETE trigger for messages won't cascade
*/
try {
((LocalFolder) mFolder).deleteAttachments(mId);
} catch (MessagingException e) {
throw new WrappedException(e);
}
db.delete("attachments", "message_id = ?", idArg);
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
((LocalFolder)mFolder).deleteHeaders(mId);
this.localStore.notifyChange();
}
/*
* Completely remove a message from the local database
*
* TODO: document how this updates the thread structure
*/
@Override
public void destroy() throws MessagingException {
try {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
UnavailableStorageException {
try {
LocalFolder localFolder = (LocalFolder) mFolder;
localFolder.deleteAttachments(mId);
if (hasThreadChildren(db, mId)) {
// This message has children in the thread structure so we need to
// make it an empty message.
ContentValues cv = new ContentValues();
cv.put("id", mId);
cv.put("folder_id", localFolder.getId());
cv.put("deleted", 0);
cv.put("message_id", getMessageId());
cv.put("empty", 1);
db.replace("messages", null, cv);
// Nothing else to do
return null;
}
// Get the message ID of the parent message if it's empty
long currentId = getEmptyThreadParent(db, mId);
// Delete the placeholder message
deleteMessageRow(db, mId);
/*
* Walk the thread tree to delete all empty parents without children
*/
while (currentId != -1) {
if (hasThreadChildren(db, currentId)) {
// We made sure there are no empty leaf nodes and can stop now.
break;
}
// Get ID of the (empty) parent for the next iteration
long newId = getEmptyThreadParent(db, currentId);
// Delete the empty message
deleteMessageRow(db, currentId);
currentId = newId;
}
} catch (MessagingException e) {
throw new WrappedException(e);
}
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
this.localStore.notifyChange();
}
/**
* Get ID of the the given message's parent if the parent is an empty message.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to get the parent for.
*
* @return Message ID of the parent message if there exists a parent and it is empty.
* Otherwise {@code -1}.
*/
private long getEmptyThreadParent(SQLiteDatabase db, long messageId) {
Cursor cursor = db.rawQuery(
"SELECT m.id " +
"FROM threads t1 " +
"JOIN threads t2 ON (t1.parent = t2.id) " +
"LEFT JOIN messages m ON (t2.message_id = m.id) " +
"WHERE t1.message_id = ? AND m.empty = 1",
new String[] { Long.toString(messageId) });
try {
return (cursor.moveToFirst() && !cursor.isNull(0)) ? cursor.getLong(0) : -1;
} finally {
cursor.close();
}
}
/**
* Check whether or not a message has child messages in the thread structure.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to get the children for.
*
* @return {@code true} if the message has children. {@code false} otherwise.
*/
private boolean hasThreadChildren(SQLiteDatabase db, long messageId) {
Cursor cursor = db.rawQuery(
"SELECT COUNT(t2.id) " +
"FROM threads t1 " +
"JOIN threads t2 ON (t2.parent = t1.id) " +
"WHERE t1.message_id = ?",
new String[] { Long.toString(messageId) });
try {
return (cursor.moveToFirst() && !cursor.isNull(0) && cursor.getLong(0) > 0L);
} finally {
cursor.close();
}
}
/**
* Delete a message from the 'messages' and 'threads' tables.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to delete.
*/
private void deleteMessageRow(SQLiteDatabase db, long messageId) {
String[] idArg = { Long.toString(messageId) };
// Delete the message
db.delete("messages", "id = ?", idArg);
// Delete row in 'threads' table
// TODO: create trigger for 'messages' table to get rid of the row in 'threads' table
db.delete("threads", "message_id = ?", idArg);
}
private void loadHeaders() throws UnavailableStorageException {
ArrayList<LocalMessage> messages = new ArrayList<LocalMessage>();
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) throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
super.addHeader(name, value);
}
@Override
public void setHeader(String name, String value) throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
super.setHeader(name, value);
}
@Override
public String[] getHeader(String name) throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
return super.getHeader(name);
}
@Override
public void removeHeader(String name) throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
super.removeHeader(name);
}
@Override
public Set<String> getHeaderNames() throws UnavailableStorageException {
if (!mHeadersLoaded)
loadHeaders();
return super.getHeaderNames();
}
@Override
public LocalMessage clone() {
LocalMessage message = new LocalMessage(this.localStore);
super.copy(message);
message.mId = mId;
message.mAttachmentCount = mAttachmentCount;
message.mSubject = mSubject;
message.mPreview = mPreview;
message.mHeadersLoaded = mHeadersLoaded;
message.mMessageDirty = mMessageDirty;
return message;
}
public long getThreadId() {
return mThreadId;
}
public long getRootId() {
return mRootId;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
package com.fsck.k9.mail.store.local;
import com.fsck.k9.mail.internet.TextBody;
public class LocalTextBody extends TextBody {
/**
* This is an HTML-ified version of the message for display purposes.
*/
private String mBodyForDisplay;
public LocalTextBody(String body) {
super(body);
}
public LocalTextBody(String body, String bodyForDisplay) {
super(body);
this.mBodyForDisplay = bodyForDisplay;
}
public String getBodyForDisplay() {
return mBodyForDisplay;
}
public void setBodyForDisplay(String mBodyForDisplay) {
this.mBodyForDisplay = mBodyForDisplay;
}
}//LocalTextBody

View File

@ -0,0 +1,599 @@
package com.fsck.k9.mail.store.local;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import android.content.ContentValues;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LockableDatabase;
import com.fsck.k9.provider.AttachmentProvider;
class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition {
/**
*
*/
private final LocalStore localStore;
/**
* @param localStore
*/
StoreSchemaDefinition(LocalStore localStore) {
this.localStore = localStore;
}
@Override
public int getVersion() {
return LocalStore.DB_VERSION;
}
@Override
public void doDbUpgrade(final SQLiteDatabase db) {
try {
upgradeDatabase(db);
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Exception while upgrading database. Resetting the DB to v0", e);
db.setVersion(0);
upgradeDatabase(db);
}
}
private void upgradeDatabase(final SQLiteDatabase db) {
Log.i(K9.LOG_TAG, String.format(Locale.US, "Upgrading database from version %d to version %d",
db.getVersion(), LocalStore.DB_VERSION));
AttachmentProvider.clear(this.localStore.mApplication);
db.beginTransaction();
try {
// schema version 29 was when we moved to incremental updates
// in the case of a new db or a < v29 db, we blow away and start from scratch
if (db.getVersion() < 29) {
db.execSQL("DROP TABLE IF EXISTS folders");
db.execSQL("CREATE TABLE folders (id INTEGER PRIMARY KEY, name TEXT, "
+ "last_updated INTEGER, unread_count INTEGER, visible_limit INTEGER, status TEXT, "
+ "push_state TEXT, last_pushed INTEGER, flagged_count INTEGER default 0, "
+ "integrate INTEGER, top_group INTEGER, poll_class TEXT, push_class TEXT, display_class TEXT, notify_class TEXT"
+ ")");
db.execSQL("CREATE INDEX IF NOT EXISTS folder_name ON folders (name)");
db.execSQL("DROP TABLE IF EXISTS messages");
db.execSQL("CREATE TABLE messages (" +
"id INTEGER PRIMARY KEY, " +
"deleted INTEGER default 0, " +
"folder_id INTEGER, " +
"uid TEXT, " +
"subject TEXT, " +
"date INTEGER, " +
"flags TEXT, " +
"sender_list TEXT, " +
"to_list TEXT, " +
"cc_list TEXT, " +
"bcc_list TEXT, " +
"reply_to_list TEXT, " +
"html_content TEXT, " +
"text_content TEXT, " +
"attachment_count INTEGER, " +
"internal_date INTEGER, " +
"message_id TEXT, " +
"preview TEXT, " +
"mime_type TEXT, "+
"normalized_subject_hash INTEGER, " +
"empty INTEGER, " +
"read INTEGER default 0, " +
"flagged INTEGER default 0, " +
"answered INTEGER default 0, " +
"forwarded INTEGER default 0" +
")");
db.execSQL("DROP TABLE IF EXISTS headers");
db.execSQL("CREATE TABLE headers (id INTEGER PRIMARY KEY, message_id INTEGER, name TEXT, value TEXT)");
db.execSQL("CREATE INDEX IF NOT EXISTS header_folder ON headers (message_id)");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_uid ON messages (uid, folder_id)");
db.execSQL("DROP INDEX IF EXISTS msg_folder_id");
db.execSQL("DROP INDEX IF EXISTS msg_folder_id_date");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date)");
db.execSQL("DROP INDEX IF EXISTS msg_empty");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
db.execSQL("DROP INDEX IF EXISTS msg_read");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
db.execSQL("DROP INDEX IF EXISTS msg_flagged");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
db.execSQL("DROP INDEX IF EXISTS msg_composite");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_composite ON messages (deleted, empty,folder_id,flagged,read)");
db.execSQL("DROP TABLE IF EXISTS threads");
db.execSQL("CREATE TABLE threads (" +
"id INTEGER PRIMARY KEY, " +
"message_id INTEGER, " +
"root INTEGER, " +
"parent INTEGER" +
")");
db.execSQL("DROP INDEX IF EXISTS threads_message_id");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id)");
db.execSQL("DROP INDEX IF EXISTS threads_root");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_root ON threads (root)");
db.execSQL("DROP INDEX IF EXISTS threads_parent");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
db.execSQL("DROP TRIGGER IF EXISTS set_thread_root");
db.execSQL("CREATE TRIGGER set_thread_root " +
"AFTER INSERT ON threads " +
"BEGIN " +
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
"END");
db.execSQL("DROP TABLE IF EXISTS attachments");
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
+ "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
+ "mime_type TEXT, content_id TEXT, content_disposition TEXT)");
db.execSQL("DROP TABLE IF EXISTS pending_commands");
db.execSQL("CREATE TABLE pending_commands " +
"(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)");
db.execSQL("DROP TRIGGER IF EXISTS delete_folder");
db.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;");
db.execSQL("DROP TRIGGER IF EXISTS delete_message");
db.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; "
+ "DELETE FROM headers where old.id = message_id; END;");
} else {
// in the case that we're starting out at 29 or newer, run all the needed updates
if (db.getVersion() < 30) {
try {
db.execSQL("ALTER TABLE messages ADD deleted INTEGER default 0");
} catch (SQLiteException e) {
if (! e.toString().startsWith("duplicate column name: deleted")) {
throw e;
}
}
}
if (db.getVersion() < 31) {
db.execSQL("DROP INDEX IF EXISTS msg_folder_id_date");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_folder_id_deleted_date ON messages (folder_id,deleted,internal_date)");
}
if (db.getVersion() < 32) {
db.execSQL("UPDATE messages SET deleted = 1 WHERE flags LIKE '%DELETED%'");
}
if (db.getVersion() < 33) {
try {
db.execSQL("ALTER TABLE messages ADD preview TEXT");
} catch (SQLiteException e) {
if (! e.toString().startsWith("duplicate column name: preview")) {
throw e;
}
}
}
if (db.getVersion() < 34) {
try {
db.execSQL("ALTER TABLE folders ADD flagged_count INTEGER default 0");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name: flagged_count")) {
throw e;
}
}
}
if (db.getVersion() < 35) {
try {
db.execSQL("update messages set flags = replace(flags, 'X_NO_SEEN_INFO', 'X_BAD_FLAG')");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to get rid of obsolete flag X_NO_SEEN_INFO", e);
}
}
if (db.getVersion() < 36) {
try {
db.execSQL("ALTER TABLE attachments ADD content_id TEXT");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to add content_id column to attachments");
}
}
if (db.getVersion() < 37) {
try {
db.execSQL("ALTER TABLE attachments ADD content_disposition TEXT");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to add content_disposition column to attachments");
}
}
// Database version 38 is solely to prune cached attachments now that we clear them better
if (db.getVersion() < 39) {
try {
db.execSQL("DELETE FROM headers WHERE id in (SELECT headers.id FROM headers LEFT JOIN messages ON headers.message_id = messages.id WHERE messages.id IS NULL)");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to remove extra header data from the database");
}
}
// V40: Store the MIME type for a message.
if (db.getVersion() < 40) {
try {
db.execSQL("ALTER TABLE messages ADD mime_type TEXT");
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Unable to add mime_type column to messages");
}
}
if (db.getVersion() < 41) {
try {
db.execSQL("ALTER TABLE folders ADD integrate INTEGER");
db.execSQL("ALTER TABLE folders ADD top_group INTEGER");
db.execSQL("ALTER TABLE folders ADD poll_class TEXT");
db.execSQL("ALTER TABLE folders ADD push_class TEXT");
db.execSQL("ALTER TABLE folders ADD display_class TEXT");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name:")) {
throw e;
}
}
Cursor cursor = null;
try {
SharedPreferences prefs = this.localStore.getPreferences();
cursor = db.rawQuery("SELECT id, name FROM folders", null);
while (cursor.moveToNext()) {
try {
int id = cursor.getInt(0);
String name = cursor.getString(1);
update41Metadata(db, prefs, id, name);
} catch (Exception e) {
Log.e(K9.LOG_TAG, " error trying to ugpgrade a folder class", e);
}
}
} catch (SQLiteException e) {
Log.e(K9.LOG_TAG, "Exception while upgrading database to v41. folder classes may have vanished", e);
} finally {
Utility.closeQuietly(cursor);
}
}
if (db.getVersion() == 41) {
try {
long startTime = System.currentTimeMillis();
SharedPreferences.Editor editor = this.localStore.getPreferences().edit();
List <? extends Folder > folders = this.localStore.getPersonalNamespaces(true);
for (Folder folder : folders) {
if (folder instanceof LocalFolder) {
LocalFolder lFolder = (LocalFolder)folder;
lFolder.save(editor);
}
}
editor.commit();
long endTime = System.currentTimeMillis();
Log.i(K9.LOG_TAG, "Putting folder preferences for " + folders.size() + " folders back into Preferences took " + (endTime - startTime) + " ms");
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Could not replace Preferences in upgrade from DB_VERSION 41", e);
}
}
if (db.getVersion() < 43) {
try {
// If folder "OUTBOX" (old, v3.800 - v3.802) exists, rename it to
// "K9MAIL_INTERNAL_OUTBOX" (new)
LocalFolder oldOutbox = new LocalFolder(this.localStore, "OUTBOX");
if (oldOutbox.exists()) {
ContentValues cv = new ContentValues();
cv.put("name", Account.OUTBOX);
db.update("folders", cv, "name = ?", new String[] { "OUTBOX" });
Log.i(K9.LOG_TAG, "Renamed folder OUTBOX to " + Account.OUTBOX);
}
// Check if old (pre v3.800) localized outbox folder exists
String localizedOutbox = K9.app.getString(R.string.special_mailbox_name_outbox);
LocalFolder obsoleteOutbox = new LocalFolder(this.localStore, localizedOutbox);
if (obsoleteOutbox.exists()) {
// Get all messages from the localized outbox ...
Message[] messages = obsoleteOutbox.getMessages(null, false);
if (messages.length > 0) {
// ... and move them to the drafts folder (we don't want to
// surprise the user by sending potentially very old messages)
LocalFolder drafts = new LocalFolder(this.localStore, this.localStore.getAccount().getDraftsFolderName());
obsoleteOutbox.moveMessages(messages, drafts);
}
// Now get rid of the localized outbox
obsoleteOutbox.delete();
obsoleteOutbox.delete(true);
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Error trying to fix the outbox folders", e);
}
}
if (db.getVersion() < 44) {
try {
db.execSQL("ALTER TABLE messages ADD thread_root INTEGER");
db.execSQL("ALTER TABLE messages ADD thread_parent INTEGER");
db.execSQL("ALTER TABLE messages ADD normalized_subject_hash INTEGER");
db.execSQL("ALTER TABLE messages ADD empty INTEGER");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name:")) {
throw e;
}
}
}
if (db.getVersion() < 45) {
try {
db.execSQL("DROP INDEX IF EXISTS msg_empty");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_empty ON messages (empty)");
db.execSQL("DROP INDEX IF EXISTS msg_thread_root");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_root ON messages (thread_root)");
db.execSQL("DROP INDEX IF EXISTS msg_thread_parent");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_thread_parent ON messages (thread_parent)");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name:")) {
throw e;
}
}
}
if (db.getVersion() < 46) {
db.execSQL("ALTER TABLE messages ADD read INTEGER default 0");
db.execSQL("ALTER TABLE messages ADD flagged INTEGER default 0");
db.execSQL("ALTER TABLE messages ADD answered INTEGER default 0");
db.execSQL("ALTER TABLE messages ADD forwarded INTEGER default 0");
String[] projection = { "id", "flags" };
ContentValues cv = new ContentValues();
List<Flag> extraFlags = new ArrayList<Flag>();
Cursor cursor = db.query("messages", projection, null, null, null, null, null);
try {
while (cursor.moveToNext()) {
long id = cursor.getLong(0);
String flagList = cursor.getString(1);
boolean read = false;
boolean flagged = false;
boolean answered = false;
boolean forwarded = false;
if (flagList != null && flagList.length() > 0) {
String[] flags = flagList.split(",");
for (String flagStr : flags) {
try {
Flag flag = Flag.valueOf(flagStr);
switch (flag) {
case ANSWERED: {
answered = true;
break;
}
case DELETED: {
// Don't store this in column 'flags'
break;
}
case FLAGGED: {
flagged = true;
break;
}
case FORWARDED: {
forwarded = true;
break;
}
case SEEN: {
read = true;
break;
}
case DRAFT:
case RECENT:
case X_DESTROYED:
case X_DOWNLOADED_FULL:
case X_DOWNLOADED_PARTIAL:
case X_GOT_ALL_HEADERS:
case X_REMOTE_COPY_STARTED:
case X_SEND_FAILED:
case X_SEND_IN_PROGRESS: {
extraFlags.add(flag);
break;
}
}
} catch (Exception e) {
// Ignore bad flags
}
}
}
cv.put("flags", this.localStore.serializeFlags(extraFlags.toArray(LocalStore.EMPTY_FLAG_ARRAY)));
cv.put("read", read);
cv.put("flagged", flagged);
cv.put("answered", answered);
cv.put("forwarded", forwarded);
db.update("messages", cv, "id = ?", new String[] { Long.toString(id) });
cv.clear();
extraFlags.clear();
}
} finally {
cursor.close();
}
db.execSQL("CREATE INDEX IF NOT EXISTS msg_read ON messages (read)");
db.execSQL("CREATE INDEX IF NOT EXISTS msg_flagged ON messages (flagged)");
}
if (db.getVersion() < 47) {
// Create new 'threads' table
db.execSQL("DROP TABLE IF EXISTS threads");
db.execSQL("CREATE TABLE threads (" +
"id INTEGER PRIMARY KEY, " +
"message_id INTEGER, " +
"root INTEGER, " +
"parent INTEGER" +
")");
// Create indices for new table
db.execSQL("DROP INDEX IF EXISTS threads_message_id");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_message_id ON threads (message_id)");
db.execSQL("DROP INDEX IF EXISTS threads_root");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_root ON threads (root)");
db.execSQL("DROP INDEX IF EXISTS threads_parent");
db.execSQL("CREATE INDEX IF NOT EXISTS threads_parent ON threads (parent)");
// Create entries for all messages in 'threads' table
db.execSQL("INSERT INTO threads (message_id) SELECT id FROM messages");
// Copy thread structure from 'messages' table to 'threads'
Cursor cursor = db.query("messages",
new String[] { "id", "thread_root", "thread_parent" },
null, null, null, null, null);
try {
ContentValues cv = new ContentValues();
while (cursor.moveToNext()) {
cv.clear();
long messageId = cursor.getLong(0);
if (!cursor.isNull(1)) {
long threadRootMessageId = cursor.getLong(1);
db.execSQL("UPDATE threads SET root = (SELECT t.id FROM " +
"threads t WHERE t.message_id = ?) " +
"WHERE message_id = ?",
new String[] {
Long.toString(threadRootMessageId),
Long.toString(messageId)
});
}
if (!cursor.isNull(2)) {
long threadParentMessageId = cursor.getLong(2);
db.execSQL("UPDATE threads SET parent = (SELECT t.id FROM " +
"threads t WHERE t.message_id = ?) " +
"WHERE message_id = ?",
new String[] {
Long.toString(threadParentMessageId),
Long.toString(messageId)
});
}
}
} finally {
cursor.close();
}
// Remove indices for old thread-related columns in 'messages' table
db.execSQL("DROP INDEX IF EXISTS msg_thread_root");
db.execSQL("DROP INDEX IF EXISTS msg_thread_parent");
// Clear out old thread-related columns in 'messages'
ContentValues cv = new ContentValues();
cv.putNull("thread_root");
cv.putNull("thread_parent");
db.update("messages", cv, null, null);
}
if (db.getVersion() < 48) {
db.execSQL("UPDATE threads SET root=id WHERE root IS NULL");
db.execSQL("CREATE TRIGGER set_thread_root " +
"AFTER INSERT ON threads " +
"BEGIN " +
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
"END");
}
if (db.getVersion() < 49) {
db.execSQL("CREATE INDEX IF NOT EXISTS msg_composite ON messages (deleted, empty,folder_id,flagged,read)");
}
if (db.getVersion() < 50) {
try {
db.execSQL("ALTER TABLE folders ADD notify_class TEXT default '" +
Folder.FolderClass.INHERITED.name() + "'");
} catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name:")) {
throw e;
}
}
ContentValues cv = new ContentValues();
cv.put("notify_class", Folder.FolderClass.FIRST_CLASS.name());
db.update("folders", cv, "name = ?",
new String[] { this.localStore.getAccount().getInboxFolderName() });
}
}
db.setVersion(LocalStore.DB_VERSION);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
if (db.getVersion() != LocalStore.DB_VERSION) {
throw new RuntimeException("Database upgrade failed!");
}
}
private void update41Metadata(final SQLiteDatabase db, SharedPreferences prefs, int id, String name) {
Folder.FolderClass displayClass = Folder.FolderClass.NO_CLASS;
Folder.FolderClass syncClass = Folder.FolderClass.INHERITED;
Folder.FolderClass pushClass = Folder.FolderClass.SECOND_CLASS;
boolean inTopGroup = false;
boolean integrate = false;
if (this.localStore.getAccount().getInboxFolderName().equals(name)) {
displayClass = Folder.FolderClass.FIRST_CLASS;
syncClass = Folder.FolderClass.FIRST_CLASS;
pushClass = Folder.FolderClass.FIRST_CLASS;
inTopGroup = true;
integrate = true;
}
try {
displayClass = Folder.FolderClass.valueOf(prefs.getString(this.localStore.uUid + "." + name + ".displayMode", displayClass.name()));
syncClass = Folder.FolderClass.valueOf(prefs.getString(this.localStore.uUid + "." + name + ".syncMode", syncClass.name()));
pushClass = Folder.FolderClass.valueOf(prefs.getString(this.localStore.uUid + "." + name + ".pushMode", pushClass.name()));
inTopGroup = prefs.getBoolean(this.localStore.uUid + "." + name + ".inTopGroup", inTopGroup);
integrate = prefs.getBoolean(this.localStore.uUid + "." + name + ".integrate", integrate);
} catch (Exception e) {
Log.e(K9.LOG_TAG, " Throwing away an error while trying to upgrade folder metadata", e);
}
if (displayClass == Folder.FolderClass.NONE) {
displayClass = Folder.FolderClass.NO_CLASS;
}
if (syncClass == Folder.FolderClass.NONE) {
syncClass = Folder.FolderClass.INHERITED;
}
if (pushClass == Folder.FolderClass.NONE) {
pushClass = Folder.FolderClass.INHERITED;
}
db.execSQL("UPDATE folders SET integrate = ?, top_group = ?, poll_class=?, push_class =?, display_class = ? WHERE id = ?",
new Object[] { integrate, inTopGroup, syncClass, pushClass, displayClass, id });
}
}

View File

@ -0,0 +1,26 @@
package com.fsck.k9.mail.store.local;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import com.fsck.k9.mail.MessagingException;
public class TempFileBody extends BinaryAttachmentBody {
private final File mFile;
public TempFileBody(String filename) {
mFile = new File(filename);
}
@Override
public InputStream getInputStream() throws MessagingException {
try {
return new FileInputStream(mFile);
} catch (FileNotFoundException e) {
return new ByteArrayInputStream(LocalStore.EMPTY_BYTE_ARRAY);
}
}
}

View File

@ -0,0 +1,36 @@
package com.fsck.k9.mail.store.local;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.CompositeBody;
import com.fsck.k9.mail.MessagingException;
public class TempFileMessageBody extends TempFileBody implements CompositeBody {
public TempFileMessageBody(String filename) {
super(filename);
}
@Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
AttachmentMessageBodyUtil.writeTo(this, out);
}
@Override
public void setUsing7bitTransport() throws MessagingException {
// see LocalAttachmentMessageBody.setUsing7bitTransport()
}
@Override
public void setEncoding(String encoding) throws MessagingException {
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
throw new MessagingException(
"Incompatible content-transfer-encoding applied to a CompositeBody");
}
mEncoding = encoding;
}
}

View File

@ -0,0 +1,17 @@
package com.fsck.k9.mail.store.local;
class ThreadInfo {
public final long threadId;
public final long msgId;
public final String messageId;
public final long rootId;
public final long parentId;
public ThreadInfo(long threadId, long msgId, String messageId, long rootId, long parentId) {
this.threadId = threadId;
this.msgId = msgId;
this.messageId = messageId;
this.rootId = rootId;
this.parentId = parentId;
}
}

View File

@ -14,7 +14,7 @@ import com.fsck.k9.mail.filter.LineWrapOutputStream;
import com.fsck.k9.mail.filter.PeekableInputStream;
import com.fsck.k9.mail.filter.SmtpDataStuffing;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.net.ssl.SslHelper;
import javax.net.ssl.SSLException;

View File

@ -15,8 +15,8 @@ import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.AttachmentInfo;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.local.LocalStore.AttachmentInfo;
import com.fsck.k9.mail.store.StorageManager;
import java.io.*;

View File

@ -11,10 +11,10 @@ import com.fsck.k9.cache.EmailProviderCacheCursor;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LockableDatabase;
import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.search.SqlQueryBuilder;

View File

@ -32,7 +32,8 @@ import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.search.SearchAccount;
import java.lang.ref.WeakReference;
@ -152,9 +153,9 @@ public class MessageProvider extends ContentProvider {
}
/**
* Extracts the {@link LocalStore.LocalMessage#getId() ID} from the given
* Extracts the {@link LocalMessage#getId() ID} from the given
* {@link MessageInfoHolder}. The underlying {@link Message} is expected to
* be a {@link LocalStore.LocalMessage}.
* be a {@link LocalMessage}.
*/
public static class IdExtractor implements FieldExtractor<MessageInfoHolder, Long> {
@Override

View File

@ -5,8 +5,8 @@ import java.util.List;
import com.fsck.k9.Account;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield;

View File

@ -40,7 +40,7 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBodyPart;
import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider;
public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {

View File

@ -55,8 +55,8 @@ import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Multipart;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns;
import org.apache.commons.io.IOUtils;
@ -626,7 +626,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
for (int i = 0; i < mp.getCount(); i++) {
renderAttachments(mp.getBodyPart(i), depth + 1, message, account, controller, listener);
}
} else if (part instanceof LocalStore.LocalAttachmentBodyPart) {
} else if (part instanceof LocalAttachmentBodyPart) {
AttachmentView view = (AttachmentView)mInflater.inflate(R.layout.message_view_attachment, null);
view.setCallback(attachmentCallback);