{@link #actionUpgradeDatabases(Context, Intent)} will call {@link K9#areDatabasesUpToDate()}
* to check if we already know whether the databases have been upgraded.
*
{@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.
*
If there was an error reading the cached database version or if it shows the databases need
diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java
index 9c27ae4de..3e2e49351 100644
--- a/src/com/fsck/k9/activity/setup/AccountSettings.java
+++ b/src/com/fsck/k9/activity/setup/AccountSettings.java
@@ -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.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.service.MailService;
diff --git a/src/com/fsck/k9/activity/setup/FolderSettings.java b/src/com/fsck/k9/activity/setup/FolderSettings.java
index 5cab0a26e..ad912f305 100644
--- a/src/com/fsck/k9/activity/setup/FolderSettings.java
+++ b/src/com/fsck/k9/activity/setup/FolderSettings.java
@@ -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.LocalStore;
+import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
import com.fsck.k9.service.MailService;
public class FolderSettings extends K9PreferenceActivity {
diff --git a/src/com/fsck/k9/cache/EmailProviderCache.java b/src/com/fsck/k9/cache/EmailProviderCache.java
index 6c923f112..179d41a28 100644
--- a/src/com/fsck/k9/cache/EmailProviderCache.java
+++ b/src/com/fsck/k9/cache/EmailProviderCache.java
@@ -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.LocalStore.LocalFolder;
+import com.fsck.k9.mail.store.local.LocalStore.LocalMessage;
import com.fsck.k9.provider.EmailProvider;
/**
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index 8c1091227..3e775c161 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -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.LocalStore;
+import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
+import com.fsck.k9.mail.store.local.LocalStore.LocalMessage;
+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;
diff --git a/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java b/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
index 0a2a1711b..c9df4a4cd 100644
--- a/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
+++ b/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
@@ -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.LocalStore;
+import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
import com.fsck.k9.service.SleepService;
import java.util.List;
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 3648e8fe3..4ed0f6f25 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -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.LocalStore;
+import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.SpecialColumns;
diff --git a/src/com/fsck/k9/fragment/MessageViewFragment.java b/src/com/fsck/k9/fragment/MessageViewFragment.java
index 5f267bb64..73fb6fbfd 100644
--- a/src/com/fsck/k9/fragment/MessageViewFragment.java
+++ b/src/com/fsck/k9/fragment/MessageViewFragment.java
@@ -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.LocalStore.LocalMessage;
import com.fsck.k9.view.AttachmentView;
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
import com.fsck.k9.view.MessageHeader;
diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java
index 45efb2a34..659b81161 100644
--- a/src/com/fsck/k9/mail/Store.java
+++ b/src/com/fsck/k9/mail/Store.java
@@ -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;
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
similarity index 99%
rename from src/com/fsck/k9/mail/store/LocalStore.java
rename to src/com/fsck/k9/mail/store/local/LocalStore.java
index 8e1aca3eb..9749531b0 100644
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -1,5 +1,5 @@
-package com.fsck.k9.mail.store;
+package com.fsck.k9.mail.store.local;
import java.io.ByteArrayInputStream;
import java.io.File;
@@ -71,7 +71,11 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.MimeUtility.ViewableContainer;
import com.fsck.k9.mail.internet.TextBody;
+import com.fsck.k9.mail.store.LockableDatabase;
+import com.fsck.k9.mail.store.StorageManager;
+import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
+import com.fsck.k9.mail.store.LockableDatabase.SchemaDefinition;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.provider.AttachmentProvider;
diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java
index cedfaeaf5..bb80239a2 100644
--- a/src/com/fsck/k9/mail/transport/SmtpTransport.java
+++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java
@@ -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.LocalStore.LocalMessage;
import com.fsck.k9.net.ssl.SslHelper;
import javax.net.ssl.SSLException;
diff --git a/src/com/fsck/k9/provider/AttachmentProvider.java b/src/com/fsck/k9/provider/AttachmentProvider.java
index f3ba78590..c0bd8b0f7 100644
--- a/src/com/fsck/k9/provider/AttachmentProvider.java
+++ b/src/com/fsck/k9/provider/AttachmentProvider.java
@@ -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.*;
diff --git a/src/com/fsck/k9/provider/EmailProvider.java b/src/com/fsck/k9/provider/EmailProvider.java
index 9537ed64a..c99bdb1f4 100644
--- a/src/com/fsck/k9/provider/EmailProvider.java
+++ b/src/com/fsck/k9/provider/EmailProvider.java
@@ -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;
diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java
index 86a858337..343f9fbe6 100644
--- a/src/com/fsck/k9/provider/MessageProvider.java
+++ b/src/com/fsck/k9/provider/MessageProvider.java
@@ -32,7 +32,7 @@ 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.LocalStore;
import com.fsck.k9.search.SearchAccount;
import java.lang.ref.WeakReference;
diff --git a/src/com/fsck/k9/search/SqlQueryBuilder.java b/src/com/fsck/k9/search/SqlQueryBuilder.java
index da6e0850f..224c0b901 100644
--- a/src/com/fsck/k9/search/SqlQueryBuilder.java
+++ b/src/com/fsck/k9/search/SqlQueryBuilder.java
@@ -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.LocalStore;
+import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield;
diff --git a/src/com/fsck/k9/view/AttachmentView.java b/src/com/fsck/k9/view/AttachmentView.java
index 9c5de50a0..405ecbd99 100644
--- a/src/com/fsck/k9/view/AttachmentView.java
+++ b/src/com/fsck/k9/view/AttachmentView.java
@@ -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.LocalStore.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider;
public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {
diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java
index 58a5821cf..db842707f 100644
--- a/src/com/fsck/k9/view/SingleMessageView.java
+++ b/src/com/fsck/k9/view/SingleMessageView.java
@@ -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.LocalStore;
+import com.fsck.k9.mail.store.local.LocalStore.LocalMessage;
import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns;
import org.apache.commons.io.IOUtils;
From f92da3af5918cc428dd121ac0fe6c3092a230205 Mon Sep 17 00:00:00 2001
From: Christian Frommeyer
Date: Sun, 7 Sep 2014 14:54:02 +0200
Subject: [PATCH 02/39] Extracting local attachment classes from LocalStore to
reduce file size.
---
src/com/fsck/k9/activity/MessageCompose.java | 6 +-
.../local/AttachmentMessageBodyUtil.java | 34 ++++
.../store/local/BinaryAttachmentBody.java | 54 ++++++
.../mail/store/local/LocalAttachmentBody.java | 37 ++++
.../local/LocalAttachmentMessageBody.java | 49 +++++
.../fsck/k9/mail/store/local/LocalStore.java | 180 +-----------------
.../k9/mail/store/local/TempFileBody.java | 26 +++
.../mail/store/local/TempFileMessageBody.java | 36 ++++
8 files changed, 240 insertions(+), 182 deletions(-)
create mode 100644 src/com/fsck/k9/mail/store/local/AttachmentMessageBodyUtil.java
create mode 100644 src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java
create mode 100644 src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java
create mode 100644 src/com/fsck/k9/mail/store/local/LocalAttachmentMessageBody.java
create mode 100644 src/com/fsck/k9/mail/store/local/TempFileBody.java
create mode 100644 src/com/fsck/k9/mail/store/local/TempFileMessageBody.java
diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java
index 35507d3ab..727f524ca 100644
--- a/src/com/fsck/k9/activity/MessageCompose.java
+++ b/src/com/fsck/k9/activity/MessageCompose.java
@@ -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.local.LocalStore.LocalAttachmentBody;
-import com.fsck.k9.mail.store.local.LocalStore.TempFileBody;
-import com.fsck.k9.mail.store.local.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;
diff --git a/src/com/fsck/k9/mail/store/local/AttachmentMessageBodyUtil.java b/src/com/fsck/k9/mail/store/local/AttachmentMessageBodyUtil.java
new file mode 100644
index 000000000..61cb8456a
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/AttachmentMessageBodyUtil.java
@@ -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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java b/src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java
new file mode 100644
index 000000000..c03fceb0d
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java b/src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java
new file mode 100644
index 000000000..34772be95
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/store/local/LocalAttachmentMessageBody.java b/src/com/fsck/k9/mail/store/local/LocalAttachmentMessageBody.java
new file mode 100644
index 000000000..5c451cf15
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/LocalAttachmentMessageBody.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/store/local/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
index 9749531b0..dde0f0f0c 100644
--- a/src/com/fsck/k9/mail/store/local/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -1,10 +1,7 @@
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.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
@@ -26,7 +23,6 @@ import java.util.UUID;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
-import org.apache.james.mime4j.codec.QuotedPrintableOutputStream;
import org.apache.james.mime4j.util.MimeUtil;
import android.app.Application;
@@ -54,7 +50,6 @@ import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
-import com.fsck.k9.mail.CompositeBody;
import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
@@ -63,7 +58,6 @@ import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.Store;
-import com.fsck.k9.mail.filter.Base64OutputStream;
import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage;
@@ -98,7 +92,7 @@ public class LocalStore extends Store implements Serializable {
private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
- private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
+ static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/*
* a String containing the columns getMessages expects to work with
@@ -4060,178 +4054,6 @@ public class LocalStore extends Store implements Serializable {
}
}
- public abstract static 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;
- }
- }
-
- public static 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(EMPTY_BYTE_ARRAY);
- }
- }
- }
-
- public static 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(EMPTY_BYTE_ARRAY);
- }
- }
-
- public Uri getContentUri() {
- return mUri;
- }
- }
-
- /**
- * A {@link LocalAttachmentBody} extension containing a message/rfc822 type body
- *
- */
- public static 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;
- }
- }
-
- public static 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;
- }
- }
-
- public static 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();
- }
- }
- }
-
static class ThreadInfo {
public final long threadId;
public final long msgId;
diff --git a/src/com/fsck/k9/mail/store/local/TempFileBody.java b/src/com/fsck/k9/mail/store/local/TempFileBody.java
new file mode 100644
index 000000000..f093998d8
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/TempFileBody.java
@@ -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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/store/local/TempFileMessageBody.java b/src/com/fsck/k9/mail/store/local/TempFileMessageBody.java
new file mode 100644
index 000000000..82188f485
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/TempFileMessageBody.java
@@ -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;
+ }
+}
\ No newline at end of file
From 89ba2c510bd9d78c0a6a244109acb30fa8fcf2fb Mon Sep 17 00:00:00 2001
From: Christian Frommeyer
Date: Sun, 7 Sep 2014 15:01:48 +0200
Subject: [PATCH 03/39] More nested classes extracted from LocalStore.
---
.../store/local/LocalAttachmentBodyPart.java | 31 +++++++++++
.../fsck/k9/mail/store/local/LocalStore.java | 55 +------------------
.../k9/mail/store/local/LocalTextBody.java | 28 ++++++++++
src/com/fsck/k9/view/AttachmentView.java | 2 +-
src/com/fsck/k9/view/SingleMessageView.java | 4 +-
5 files changed, 64 insertions(+), 56 deletions(-)
create mode 100644 src/com/fsck/k9/mail/store/local/LocalAttachmentBodyPart.java
create mode 100644 src/com/fsck/k9/mail/store/local/LocalTextBody.java
diff --git a/src/com/fsck/k9/mail/store/local/LocalAttachmentBodyPart.java b/src/com/fsck/k9/mail/store/local/LocalAttachmentBodyPart.java
new file mode 100644
index 000000000..9fcdf71b3
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/LocalAttachmentBodyPart.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/store/local/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
index dde0f0f0c..327cf5388 100644
--- a/src/com/fsck/k9/mail/store/local/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -3473,31 +3473,6 @@ public class LocalStore extends Store implements Serializable {
}
}
- public static 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
-
public class LocalMessage extends MimeMessage {
private long mId;
private int mAttachmentCount;
@@ -3593,8 +3568,8 @@ public class LocalStore extends Store implements Serializable {
if (part == null) {
// If that fails, try and get a text part.
part = MimeUtility.findFirstPartByMimeType(this, "text/plain");
- if (part != null && part.getBody() instanceof LocalStore.LocalTextBody) {
- text = ((LocalStore.LocalTextBody) part.getBody()).getBodyForDisplay();
+ 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.
@@ -4028,32 +4003,6 @@ public class LocalStore extends Store implements Serializable {
}
}
- public static 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;
- }
- }
-
static class ThreadInfo {
public final long threadId;
public final long msgId;
diff --git a/src/com/fsck/k9/mail/store/local/LocalTextBody.java b/src/com/fsck/k9/mail/store/local/LocalTextBody.java
new file mode 100644
index 000000000..c28bccfee
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/LocalTextBody.java
@@ -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
\ No newline at end of file
diff --git a/src/com/fsck/k9/view/AttachmentView.java b/src/com/fsck/k9/view/AttachmentView.java
index 405ecbd99..cffb37c2a 100644
--- a/src/com/fsck/k9/view/AttachmentView.java
+++ b/src/com/fsck/k9/view/AttachmentView.java
@@ -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.local.LocalStore.LocalAttachmentBodyPart;
+import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
import com.fsck.k9.provider.AttachmentProvider;
public class AttachmentView extends FrameLayout implements OnClickListener, OnLongClickListener {
diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java
index db842707f..f791f030c 100644
--- a/src/com/fsck/k9/view/SingleMessageView.java
+++ b/src/com/fsck/k9/view/SingleMessageView.java
@@ -55,7 +55,7 @@ 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.local.LocalStore;
+import com.fsck.k9.mail.store.local.LocalAttachmentBodyPart;
import com.fsck.k9.mail.store.local.LocalStore.LocalMessage;
import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns;
@@ -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);
From 91ef5fa816b1e2265af49cace75793dd22732bec Mon Sep 17 00:00:00 2001
From: Christian Frommeyer
Date: Sun, 7 Sep 2014 16:01:33 +0200
Subject: [PATCH 04/39] Extracted LocalFolder and LocalMessage definition from
LocalStore
---
src/com/fsck/k9/activity/FolderList.java | 2 +-
.../k9/activity/setup/AccountSettings.java | 2 +-
.../k9/activity/setup/FolderSettings.java | 2 +-
src/com/fsck/k9/cache/EmailProviderCache.java | 4 +-
.../k9/controller/MessagingController.java | 4 +-
.../MessagingControllerPushReceiver.java | 2 +-
.../fsck/k9/fragment/MessageListFragment.java | 2 +-
.../fsck/k9/fragment/MessageViewFragment.java | 2 +-
.../fsck/k9/mail/store/local/LocalFolder.java | 2197 +++++++++++++
.../k9/mail/store/local/LocalMessage.java | 559 ++++
.../fsck/k9/mail/store/local/LocalStore.java | 2776 +----------------
.../fsck/k9/mail/store/local/ThreadInfo.java | 17 +
.../fsck/k9/mail/transport/SmtpTransport.java | 2 +-
src/com/fsck/k9/provider/MessageProvider.java | 5 +-
src/com/fsck/k9/search/SqlQueryBuilder.java | 2 +-
src/com/fsck/k9/view/SingleMessageView.java | 2 +-
16 files changed, 2822 insertions(+), 2758 deletions(-)
create mode 100644 src/com/fsck/k9/mail/store/local/LocalFolder.java
create mode 100644 src/com/fsck/k9/mail/store/local/LocalMessage.java
create mode 100644 src/com/fsck/k9/mail/store/local/ThreadInfo.java
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index 3903ec0dd..bd5be538b 100644
--- a/src/com/fsck/k9/activity/FolderList.java
+++ b/src/com/fsck/k9/activity/FolderList.java
@@ -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.local.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;
diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java
index 3e2e49351..5d2f34fce 100644
--- a/src/com/fsck/k9/activity/setup/AccountSettings.java
+++ b/src/com/fsck/k9/activity/setup/AccountSettings.java
@@ -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.local.LocalStore.LocalFolder;
+import com.fsck.k9.mail.store.local.LocalFolder;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.service.MailService;
diff --git a/src/com/fsck/k9/activity/setup/FolderSettings.java b/src/com/fsck/k9/activity/setup/FolderSettings.java
index ad912f305..31bb8e95c 100644
--- a/src/com/fsck/k9/activity/setup/FolderSettings.java
+++ b/src/com/fsck/k9/activity/setup/FolderSettings.java
@@ -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.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
-import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
import com.fsck.k9.service.MailService;
public class FolderSettings extends K9PreferenceActivity {
diff --git a/src/com/fsck/k9/cache/EmailProviderCache.java b/src/com/fsck/k9/cache/EmailProviderCache.java
index 179d41a28..ba3741f49 100644
--- a/src/com/fsck/k9/cache/EmailProviderCache.java
+++ b/src/com/fsck/k9/cache/EmailProviderCache.java
@@ -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.local.LocalStore.LocalFolder;
-import com.fsck.k9.mail.store.local.LocalStore.LocalMessage;
+import com.fsck.k9.mail.store.local.LocalFolder;
+import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.provider.EmailProvider;
/**
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index 3e775c161..7b979371b 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -79,9 +79,9 @@ 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.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.LocalFolder;
-import com.fsck.k9.mail.store.local.LocalStore.LocalMessage;
import com.fsck.k9.mail.store.local.LocalStore.PendingCommand;
import com.fsck.k9.mail.store.Pop3Store;
import com.fsck.k9.mail.store.UnavailableAccountException;
diff --git a/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java b/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
index c9df4a4cd..ccd13a59f 100644
--- a/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
+++ b/src/com/fsck/k9/controller/MessagingControllerPushReceiver.java
@@ -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.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
-import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
import com.fsck.k9.service.SleepService;
import java.util.List;
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index 4ed0f6f25..e72da130d 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -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.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
-import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.provider.EmailProvider.SpecialColumns;
diff --git a/src/com/fsck/k9/fragment/MessageViewFragment.java b/src/com/fsck/k9/fragment/MessageViewFragment.java
index 73fb6fbfd..ec1219780 100644
--- a/src/com/fsck/k9/fragment/MessageViewFragment.java
+++ b/src/com/fsck/k9/fragment/MessageViewFragment.java
@@ -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.local.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;
diff --git a/src/com/fsck/k9/mail/store/local/LocalFolder.java b/src/com/fsck/k9/mail/store/local/LocalFolder.java
new file mode 100644
index 000000000..009d630ae
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/LocalFolder.java
@@ -0,0 +1,2197 @@
+package com.fsck.k9.mail.store.local;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.james.mime4j.util.MimeUtil;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.net.Uri;
+import android.util.Log;
+
+import com.fsck.k9.K9;
+import com.fsck.k9.Account.MessageFormat;
+import com.fsck.k9.activity.Search;
+import com.fsck.k9.controller.MessageRemovalListener;
+import com.fsck.k9.controller.MessageRetrievalListener;
+import com.fsck.k9.helper.HtmlConverter;
+import com.fsck.k9.helper.Utility;
+import com.fsck.k9.mail.Address;
+import com.fsck.k9.mail.Body;
+import com.fsck.k9.mail.BodyPart;
+import com.fsck.k9.mail.FetchProfile;
+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.Part;
+import com.fsck.k9.mail.Message.RecipientType;
+import com.fsck.k9.mail.internet.MimeBodyPart;
+import com.fsck.k9.mail.internet.MimeHeader;
+import com.fsck.k9.mail.internet.MimeMessage;
+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.MimeUtility.ViewableContainer;
+import com.fsck.k9.mail.store.StorageManager;
+import com.fsck.k9.mail.store.UnavailableStorageException;
+import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
+import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
+import com.fsck.k9.provider.AttachmentProvider;
+
+public class LocalFolder extends Folder implements Serializable {
+
+ private static final long serialVersionUID = -1973296520918624767L;
+
+ private final LocalStore localStore;
+
+ private String mName = null;
+ private long mFolderId = -1;
+ private int mVisibleLimit = -1;
+ private String prefId = null;
+ private FolderClass mDisplayClass = FolderClass.NO_CLASS;
+ private FolderClass mSyncClass = FolderClass.INHERITED;
+ private FolderClass mPushClass = FolderClass.SECOND_CLASS;
+ private FolderClass mNotifyClass = FolderClass.INHERITED;
+ private boolean mInTopGroup = false;
+ private String mPushState = null;
+ private boolean mIntegrate = false;
+ // mLastUid is used during syncs. It holds the highest UID within the local folder so we
+ // know whether or not an unread message added to the local folder is actually "new" or not.
+ private Integer mLastUid = null;
+
+ public LocalFolder(LocalStore localStore, String name) {
+ super(localStore.getAccount());
+ this.localStore = localStore;
+ this.mName = name;
+
+ if (this.localStore.getAccount().getInboxFolderName().equals(getName())) {
+
+ mSyncClass = FolderClass.FIRST_CLASS;
+ mPushClass = FolderClass.FIRST_CLASS;
+ mInTopGroup = true;
+ }
+
+
+ }
+
+ public LocalFolder(LocalStore localStore, long id) {
+ super(localStore.getAccount());
+ this.localStore = localStore;
+ this.mFolderId = id;
+ }
+
+ public long getId() {
+ return mFolderId;
+ }
+
+ @Override
+ public void open(final int mode) throws MessagingException {
+
+ if (isOpen() && (getMode() == mode || mode == OPEN_MODE_RO)) {
+ return;
+ } else if (isOpen()) {
+ //previously opened in READ_ONLY and now requesting READ_WRITE
+ //so close connection and reopen
+ close();
+ }
+
+ try {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
+ Cursor cursor = null;
+ try {
+ String baseQuery = "SELECT " + LocalStore.GET_FOLDER_COLS + " FROM folders ";
+
+ if (mName != null) {
+ cursor = db.rawQuery(baseQuery + "where folders.name = ?", new String[] { mName });
+ } else {
+ cursor = db.rawQuery(baseQuery + "where folders.id = ?", new String[] { Long.toString(mFolderId) });
+ }
+
+ if (cursor.moveToFirst() && !cursor.isNull(LocalStore.FOLDER_ID_INDEX)) {
+ int folderId = cursor.getInt(LocalStore.FOLDER_ID_INDEX);
+ if (folderId > 0) {
+ open(cursor);
+ }
+ } else {
+ Log.w(K9.LOG_TAG, "Creating folder " + getName() + " with existing id " + getId());
+ create(FolderType.HOLDS_MESSAGES);
+ open(mode);
+ }
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ return null;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ void open(Cursor cursor) throws MessagingException {
+ mFolderId = cursor.getInt(LocalStore.FOLDER_ID_INDEX);
+ mName = cursor.getString(LocalStore.FOLDER_NAME_INDEX);
+ mVisibleLimit = cursor.getInt(LocalStore.FOLDER_VISIBLE_LIMIT_INDEX);
+ mPushState = cursor.getString(LocalStore.FOLDER_PUSH_STATE_INDEX);
+ super.setStatus(cursor.getString(LocalStore.FOLDER_STATUS_INDEX));
+ // Only want to set the local variable stored in the super class. This class
+ // does a DB update on setLastChecked
+ super.setLastChecked(cursor.getLong(LocalStore.FOLDER_LAST_CHECKED_INDEX));
+ super.setLastPush(cursor.getLong(LocalStore.FOLDER_LAST_PUSHED_INDEX));
+ mInTopGroup = (cursor.getInt(LocalStore.FOLDER_TOP_GROUP_INDEX)) == 1 ? true : false;
+ mIntegrate = (cursor.getInt(LocalStore.FOLDER_INTEGRATE_INDEX) == 1) ? true : false;
+ String noClass = FolderClass.NO_CLASS.toString();
+ String displayClass = cursor.getString(LocalStore.FOLDER_DISPLAY_CLASS_INDEX);
+ mDisplayClass = Folder.FolderClass.valueOf((displayClass == null) ? noClass : displayClass);
+ String notifyClass = cursor.getString(LocalStore.FOLDER_NOTIFY_CLASS_INDEX);
+ mNotifyClass = Folder.FolderClass.valueOf((notifyClass == null) ? noClass : notifyClass);
+ String pushClass = cursor.getString(LocalStore.FOLDER_PUSH_CLASS_INDEX);
+ mPushClass = Folder.FolderClass.valueOf((pushClass == null) ? noClass : pushClass);
+ String syncClass = cursor.getString(LocalStore.FOLDER_SYNC_CLASS_INDEX);
+ mSyncClass = Folder.FolderClass.valueOf((syncClass == null) ? noClass : syncClass);
+ }
+
+ @Override
+ public boolean isOpen() {
+ return (mFolderId != -1 && mName != null);
+ }
+
+ @Override
+ public int getMode() {
+ return OPEN_MODE_RW;
+ }
+
+ @Override
+ public String getName() {
+ return mName;
+ }
+
+ @Override
+ public boolean exists() throws MessagingException {
+ return this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Boolean doDbWork(final SQLiteDatabase db) throws WrappedException {
+ Cursor cursor = null;
+ try {
+ cursor = db.rawQuery("SELECT id FROM folders "
+ + "where folders.name = ?", new String[] { LocalFolder.
+ this.getName()
+ });
+ if (cursor.moveToFirst()) {
+ int folderId = cursor.getInt(0);
+ return (folderId > 0);
+ }
+
+ return false;
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ }
+ });
+ }
+
+ @Override
+ public boolean create(FolderType type) throws MessagingException {
+ return create(type, mAccount.getDisplayCount());
+ }
+
+ @Override
+ public boolean create(FolderType type, final int visibleLimit) throws MessagingException {
+ if (exists()) {
+ throw new MessagingException("Folder " + mName + " already exists.");
+ }
+ List foldersToCreate = new ArrayList(1);
+ foldersToCreate.add(this);
+ this.localStore.createFolders(foldersToCreate, visibleLimit);
+
+ return true;
+ }
+
+ class PreferencesHolder {
+ FolderClass displayClass = mDisplayClass;
+ FolderClass syncClass = mSyncClass;
+ FolderClass notifyClass = mNotifyClass;
+ FolderClass pushClass = mPushClass;
+ boolean inTopGroup = mInTopGroup;
+ boolean integrate = mIntegrate;
+ }
+
+ @Override
+ public void close() {
+ mFolderId = -1;
+ }
+
+ @Override
+ public int getMessageCount() throws MessagingException {
+ try {
+ return this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Integer doDbWork(final SQLiteDatabase db) throws WrappedException {
+ try {
+ open(OPEN_MODE_RW);
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ Cursor cursor = null;
+ try {
+ cursor = db.rawQuery("SELECT COUNT(id) FROM messages WHERE (empty IS NULL OR empty != 1) AND deleted = 0 and folder_id = ?",
+ new String[] {
+ Long.toString(mFolderId)
+ });
+ cursor.moveToFirst();
+ return cursor.getInt(0); //messagecount
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public int getUnreadMessageCount() throws MessagingException {
+ if (mFolderId == -1) {
+ open(OPEN_MODE_RW);
+ }
+
+ try {
+ return this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Integer doDbWork(final SQLiteDatabase db) throws WrappedException {
+ int unreadMessageCount = 0;
+ Cursor cursor = db.query("messages", new String[] { "COUNT(id)" },
+ "folder_id = ? AND (empty IS NULL OR empty != 1) AND deleted = 0 AND read=0",
+ new String[] { Long.toString(mFolderId) }, null, null, null);
+
+ try {
+ if (cursor.moveToFirst()) {
+ unreadMessageCount = cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return unreadMessageCount;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public int getFlaggedMessageCount() throws MessagingException {
+ if (mFolderId == -1) {
+ open(OPEN_MODE_RW);
+ }
+
+ try {
+ return this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Integer doDbWork(final SQLiteDatabase db) throws WrappedException {
+ int flaggedMessageCount = 0;
+ Cursor cursor = db.query("messages", new String[] { "COUNT(id)" },
+ "folder_id = ? AND (empty IS NULL OR empty != 1) AND deleted = 0 AND flagged = 1",
+ new String[] { Long.toString(mFolderId) }, null, null, null);
+
+ try {
+ if (cursor.moveToFirst()) {
+ flaggedMessageCount = cursor.getInt(0);
+ }
+ } finally {
+ cursor.close();
+ }
+
+ return flaggedMessageCount;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public void setLastChecked(final long lastChecked) throws MessagingException {
+ try {
+ open(OPEN_MODE_RW);
+ LocalFolder.super.setLastChecked(lastChecked);
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ updateFolderColumn("last_updated", lastChecked);
+ }
+
+ @Override
+ public void setLastPush(final long lastChecked) throws MessagingException {
+ try {
+ open(OPEN_MODE_RW);
+ LocalFolder.super.setLastPush(lastChecked);
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ updateFolderColumn("last_pushed", lastChecked);
+ }
+
+ public int getVisibleLimit() throws MessagingException {
+ open(OPEN_MODE_RW);
+ return mVisibleLimit;
+ }
+
+ public void purgeToVisibleLimit(MessageRemovalListener listener) throws MessagingException {
+ //don't purge messages while a Search is active since it might throw away search results
+ if (!Search.isActive()) {
+ if (mVisibleLimit == 0) {
+ return ;
+ }
+ open(OPEN_MODE_RW);
+ Message[] messages = getMessages(null, false);
+ for (int i = mVisibleLimit; i < messages.length; i++) {
+ if (listener != null) {
+ listener.messageRemoved(messages[i]);
+ }
+ messages[i].destroy();
+ }
+ }
+ }
+
+
+ public void setVisibleLimit(final int visibleLimit) throws MessagingException {
+ mVisibleLimit = visibleLimit;
+ updateFolderColumn("visible_limit", mVisibleLimit);
+ }
+
+ @Override
+ public void setStatus(final String status) throws MessagingException {
+ updateFolderColumn("status", status);
+ }
+ public void setPushState(final String pushState) throws MessagingException {
+ mPushState = pushState;
+ updateFolderColumn("push_state", pushState);
+ }
+
+ private void updateFolderColumn(final String column, final Object value) throws MessagingException {
+ try {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
+ try {
+ open(OPEN_MODE_RW);
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ db.execSQL("UPDATE folders SET " + column + " = ? WHERE id = ?", new Object[] { value, mFolderId });
+ return null;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ public String getPushState() {
+ return mPushState;
+ }
+
+ @Override
+ public FolderClass getDisplayClass() {
+ return mDisplayClass;
+ }
+
+ @Override
+ public FolderClass getSyncClass() {
+ return (FolderClass.INHERITED == mSyncClass) ? getDisplayClass() : mSyncClass;
+ }
+
+ public FolderClass getRawSyncClass() {
+ return mSyncClass;
+ }
+
+ public FolderClass getNotifyClass() {
+ return (FolderClass.INHERITED == mNotifyClass) ? getPushClass() : mNotifyClass;
+ }
+
+ public FolderClass getRawNotifyClass() {
+ return mNotifyClass;
+ }
+
+ @Override
+ public FolderClass getPushClass() {
+ return (FolderClass.INHERITED == mPushClass) ? getSyncClass() : mPushClass;
+ }
+
+ public FolderClass getRawPushClass() {
+ return mPushClass;
+ }
+
+ public void setDisplayClass(FolderClass displayClass) throws MessagingException {
+ mDisplayClass = displayClass;
+ updateFolderColumn("display_class", mDisplayClass.name());
+
+ }
+
+ public void setSyncClass(FolderClass syncClass) throws MessagingException {
+ mSyncClass = syncClass;
+ updateFolderColumn("poll_class", mSyncClass.name());
+ }
+
+ public void setPushClass(FolderClass pushClass) throws MessagingException {
+ mPushClass = pushClass;
+ updateFolderColumn("push_class", mPushClass.name());
+ }
+
+ public void setNotifyClass(FolderClass notifyClass) throws MessagingException {
+ mNotifyClass = notifyClass;
+ updateFolderColumn("notify_class", mNotifyClass.name());
+ }
+
+ public boolean isIntegrate() {
+ return mIntegrate;
+ }
+
+ public void setIntegrate(boolean integrate) throws MessagingException {
+ mIntegrate = integrate;
+ updateFolderColumn("integrate", mIntegrate ? 1 : 0);
+ }
+
+ private String getPrefId(String name) {
+ if (prefId == null) {
+ prefId = this.localStore.uUid + "." + name;
+ }
+
+ return prefId;
+ }
+
+ private String getPrefId() throws MessagingException {
+ open(OPEN_MODE_RW);
+ return getPrefId(mName);
+
+ }
+
+ public void delete() throws MessagingException {
+ String id = getPrefId();
+
+ SharedPreferences.Editor editor = this.localStore.getPreferences().edit();
+
+ editor.remove(id + ".displayMode");
+ editor.remove(id + ".syncMode");
+ editor.remove(id + ".pushMode");
+ editor.remove(id + ".inTopGroup");
+ editor.remove(id + ".integrate");
+
+ editor.commit();
+ }
+
+ public void save() throws MessagingException {
+ SharedPreferences.Editor editor = this.localStore.getPreferences().edit();
+ save(editor);
+ editor.commit();
+ }
+
+ public void save(SharedPreferences.Editor editor) throws MessagingException {
+ String id = getPrefId();
+
+ // there can be a lot of folders. For the defaults, let's not save prefs, saving space, except for INBOX
+ if (mDisplayClass == FolderClass.NO_CLASS && !mAccount.getInboxFolderName().equals(getName())) {
+ editor.remove(id + ".displayMode");
+ } else {
+ editor.putString(id + ".displayMode", mDisplayClass.name());
+ }
+
+ if (mSyncClass == FolderClass.INHERITED && !mAccount.getInboxFolderName().equals(getName())) {
+ editor.remove(id + ".syncMode");
+ } else {
+ editor.putString(id + ".syncMode", mSyncClass.name());
+ }
+
+ if (mNotifyClass == FolderClass.INHERITED && !mAccount.getInboxFolderName().equals(getName())) {
+ editor.remove(id + ".notifyMode");
+ } else {
+ editor.putString(id + ".notifyMode", mNotifyClass.name());
+ }
+
+ if (mPushClass == FolderClass.SECOND_CLASS && !mAccount.getInboxFolderName().equals(getName())) {
+ editor.remove(id + ".pushMode");
+ } else {
+ editor.putString(id + ".pushMode", mPushClass.name());
+ }
+ editor.putBoolean(id + ".inTopGroup", mInTopGroup);
+
+ editor.putBoolean(id + ".integrate", mIntegrate);
+
+ }
+
+ public void refresh(String name, PreferencesHolder prefHolder) {
+ String id = getPrefId(name);
+
+ SharedPreferences preferences = this.localStore.getPreferences();
+
+ try {
+ prefHolder.displayClass = FolderClass.valueOf(preferences.getString(id + ".displayMode",
+ prefHolder.displayClass.name()));
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "Unable to load displayMode for " + getName(), e);
+ }
+ if (prefHolder.displayClass == FolderClass.NONE) {
+ prefHolder.displayClass = FolderClass.NO_CLASS;
+ }
+
+ try {
+ prefHolder.syncClass = FolderClass.valueOf(preferences.getString(id + ".syncMode",
+ prefHolder.syncClass.name()));
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "Unable to load syncMode for " + getName(), e);
+
+ }
+ if (prefHolder.syncClass == FolderClass.NONE) {
+ prefHolder.syncClass = FolderClass.INHERITED;
+ }
+
+ try {
+ prefHolder.notifyClass = FolderClass.valueOf(preferences.getString(id + ".notifyMode",
+ prefHolder.notifyClass.name()));
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "Unable to load notifyMode for " + getName(), e);
+ }
+ if (prefHolder.notifyClass == FolderClass.NONE) {
+ prefHolder.notifyClass = FolderClass.INHERITED;
+ }
+
+ try {
+ prefHolder.pushClass = FolderClass.valueOf(preferences.getString(id + ".pushMode",
+ prefHolder.pushClass.name()));
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "Unable to load pushMode for " + getName(), e);
+ }
+ if (prefHolder.pushClass == FolderClass.NONE) {
+ prefHolder.pushClass = FolderClass.INHERITED;
+ }
+ prefHolder.inTopGroup = preferences.getBoolean(id + ".inTopGroup", prefHolder.inTopGroup);
+ prefHolder.integrate = preferences.getBoolean(id + ".integrate", prefHolder.integrate);
+
+ }
+
+ @Override
+ public void fetch(final Message[] messages, final FetchProfile fp, final MessageRetrievalListener listener)
+ throws MessagingException {
+ try {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
+ try {
+ open(OPEN_MODE_RW);
+ if (fp.contains(FetchProfile.Item.BODY)) {
+ for (Message message : messages) {
+ LocalMessage localMessage = (LocalMessage)message;
+ Cursor cursor = null;
+ MimeMultipart mp = new MimeMultipart();
+ mp.setSubType("mixed");
+ try {
+ cursor = db.rawQuery("SELECT html_content, text_content, mime_type FROM messages "
+ + "WHERE id = ?",
+ new String[] { Long.toString(localMessage.mId) });
+ cursor.moveToNext();
+ String htmlContent = cursor.getString(0);
+ String textContent = cursor.getString(1);
+ String mimeType = cursor.getString(2);
+ if (mimeType != null && mimeType.toLowerCase(Locale.US).startsWith("multipart/")) {
+ // If this is a multipart message, preserve both text
+ // and html parts, as well as the subtype.
+ mp.setSubType(mimeType.toLowerCase(Locale.US).replaceFirst("^multipart/", ""));
+ if (textContent != null) {
+ LocalTextBody body = new LocalTextBody(textContent, htmlContent);
+ MimeBodyPart bp = new MimeBodyPart(body, "text/plain");
+ mp.addBodyPart(bp);
+ }
+
+ if (mAccount.getMessageFormat() != MessageFormat.TEXT) {
+ if (htmlContent != null) {
+ TextBody body = new TextBody(htmlContent);
+ MimeBodyPart bp = new MimeBodyPart(body, "text/html");
+ mp.addBodyPart(bp);
+ }
+
+ // If we have both text and html content and our MIME type
+ // isn't multipart/alternative, then corral them into a new
+ // multipart/alternative part and put that into the parent.
+ // If it turns out that this is the only part in the parent
+ // MimeMultipart, it'll get fixed below before we attach to
+ // the message.
+ if (textContent != null && htmlContent != null && !mimeType.equalsIgnoreCase("multipart/alternative")) {
+ MimeMultipart alternativeParts = mp;
+ alternativeParts.setSubType("alternative");
+ mp = new MimeMultipart();
+ mp.addBodyPart(new MimeBodyPart(alternativeParts));
+ }
+ }
+ } else if (mimeType != null && mimeType.equalsIgnoreCase("text/plain")) {
+ // If it's text, add only the plain part. The MIME
+ // container will drop away below.
+ if (textContent != null) {
+ LocalTextBody body = new LocalTextBody(textContent, htmlContent);
+ MimeBodyPart bp = new MimeBodyPart(body, "text/plain");
+ mp.addBodyPart(bp);
+ }
+ } else if (mimeType != null && mimeType.equalsIgnoreCase("text/html")) {
+ // If it's html, add only the html part. The MIME
+ // container will drop away below.
+ if (htmlContent != null) {
+ TextBody body = new TextBody(htmlContent);
+ MimeBodyPart bp = new MimeBodyPart(body, "text/html");
+ mp.addBodyPart(bp);
+ }
+ } else {
+ // MIME type not set. Grab whatever part we can get,
+ // with Text taking precedence. This preserves pre-HTML
+ // composition behaviour.
+ if (textContent != null) {
+ LocalTextBody body = new LocalTextBody(textContent, htmlContent);
+ MimeBodyPart bp = new MimeBodyPart(body, "text/plain");
+ mp.addBodyPart(bp);
+ } else if (htmlContent != null) {
+ TextBody body = new TextBody(htmlContent);
+ MimeBodyPart bp = new MimeBodyPart(body, "text/html");
+ mp.addBodyPart(bp);
+ }
+ }
+
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "Exception fetching message:", e);
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+
+ try {
+ cursor = db.query(
+ "attachments",
+ new String[] {
+ "id",
+ "size",
+ "name",
+ "mime_type",
+ "store_data",
+ "content_uri",
+ "content_id",
+ "content_disposition"
+ },
+ "message_id = ?",
+ new String[] { Long.toString(localMessage.mId) },
+ null,
+ null,
+ null);
+
+ while (cursor.moveToNext()) {
+ long id = cursor.getLong(0);
+ int size = cursor.getInt(1);
+ String name = cursor.getString(2);
+ String type = cursor.getString(3);
+ String storeData = cursor.getString(4);
+ String contentUri = cursor.getString(5);
+ String contentId = cursor.getString(6);
+ String contentDisposition = cursor.getString(7);
+ String encoding = MimeUtility.getEncodingforType(type);
+ Body body = null;
+
+ if (contentDisposition == null) {
+ contentDisposition = "attachment";
+ }
+
+ if (contentUri != null) {
+ if (MimeUtil.isMessage(type)) {
+ body = new LocalAttachmentMessageBody(
+ Uri.parse(contentUri),
+ LocalFolder.this.localStore.mApplication);
+ } else {
+ body = new LocalAttachmentBody(
+ Uri.parse(contentUri),
+ LocalFolder.this.localStore.mApplication);
+ }
+ }
+
+ MimeBodyPart bp = new LocalAttachmentBodyPart(body, id);
+ bp.setEncoding(encoding);
+ if (name != null) {
+ bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
+ String.format("%s;\r\n name=\"%s\"",
+ type,
+ name));
+ bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
+ String.format(Locale.US, "%s;\r\n filename=\"%s\";\r\n size=%d",
+ contentDisposition,
+ name, // TODO: Should use encoded word defined in RFC 2231.
+ size));
+ } else {
+ bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE, type);
+ bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
+ String.format(Locale.US, "%s;\r\n size=%d",
+ contentDisposition,
+ size));
+ }
+
+ bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
+ /*
+ * HEADER_ANDROID_ATTACHMENT_STORE_DATA is a custom header we add to that
+ * we can later pull the attachment from the remote store if necessary.
+ */
+ bp.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, storeData);
+
+ mp.addBodyPart(bp);
+ }
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+
+ if (mp.getCount() == 0) {
+ // If we have no body, remove the container and create a
+ // dummy plain text body. This check helps prevents us from
+ // triggering T_MIME_NO_TEXT and T_TVD_MIME_NO_HEADERS
+ // SpamAssassin rules.
+ localMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "text/plain");
+ localMessage.setBody(new TextBody(""));
+ } else if (mp.getCount() == 1 && (mp.getBodyPart(0) instanceof LocalAttachmentBodyPart) == false)
+
+ {
+ // If we have only one part, drop the MimeMultipart container.
+ BodyPart part = mp.getBodyPart(0);
+ localMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, part.getContentType());
+ localMessage.setBody(part.getBody());
+ } else {
+ // Otherwise, attach the MimeMultipart to the message.
+ localMessage.setBody(mp);
+ }
+ }
+ }
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ return null;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
+ throws MessagingException {
+ open(OPEN_MODE_RW);
+ throw new MessagingException(
+ "LocalStore.getMessages(int, int, MessageRetrievalListener) not yet implemented");
+ }
+
+ /**
+ * Populate the header fields of the given list of messages by reading
+ * the saved header data from the database.
+ *
+ * @param messages
+ * The messages whose headers should be loaded.
+ * @throws UnavailableStorageException
+ */
+ void populateHeaders(final List messages) throws UnavailableStorageException {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ Cursor cursor = null;
+ if (messages.isEmpty()) {
+ return null;
+ }
+ try {
+ Map popMessages = new HashMap();
+ List ids = new ArrayList();
+ StringBuilder questions = new StringBuilder();
+
+ for (int i = 0; i < messages.size(); i++) {
+ if (i != 0) {
+ questions.append(", ");
+ }
+ questions.append("?");
+ LocalMessage message = messages.get(i);
+ Long id = message.getId();
+ ids.add(Long.toString(id));
+ popMessages.put(id, message);
+
+ }
+
+ cursor = db.rawQuery(
+ "SELECT message_id, name, value FROM headers " + "WHERE message_id in ( " + questions + ") ORDER BY id ASC",
+ ids.toArray(LocalStore.EMPTY_STRING_ARRAY));
+
+
+ while (cursor.moveToNext()) {
+ Long id = cursor.getLong(0);
+ String name = cursor.getString(1);
+ String value = cursor.getString(2);
+ //Log.i(K9.LOG_TAG, "Retrieved header name= " + name + ", value = " + value + " for message " + id);
+ popMessages.get(id).addHeader(name, value);
+ }
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ return null;
+ }
+ });
+ }
+
+ public String getMessageUidById(final long id) throws MessagingException {
+ try {
+ return this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public String doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ open(OPEN_MODE_RW);
+ Cursor cursor = null;
+
+ try {
+ cursor = db.rawQuery(
+ "SELECT uid FROM messages " +
+ "WHERE id = ? AND folder_id = ?",
+ new String[] {
+ Long.toString(id), Long.toString(mFolderId)
+ });
+ if (!cursor.moveToNext()) {
+ return null;
+ }
+ return cursor.getString(0);
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public LocalMessage getMessage(final String uid) throws MessagingException {
+ try {
+ return this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public LocalMessage doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ open(OPEN_MODE_RW);
+ LocalMessage message = new LocalMessage(LocalFolder.this.localStore, uid, LocalFolder.this);
+ Cursor cursor = null;
+
+ try {
+ cursor = db.rawQuery(
+ "SELECT " +
+ LocalStore.GET_MESSAGES_COLS +
+ "FROM messages " +
+ "LEFT JOIN threads ON (threads.message_id = messages.id) " +
+ "WHERE uid = ? AND folder_id = ?",
+ new String[] {
+ message.getUid(), Long.toString(mFolderId)
+ });
+ if (!cursor.moveToNext()) {
+ return null;
+ }
+ message.populateFromGetMessageCursor(cursor);
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ return message;
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
+ return getMessages(listener, true);
+ }
+
+ @Override
+ public Message[] getMessages(final MessageRetrievalListener listener, final boolean includeDeleted) throws MessagingException {
+ try {
+ return this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ open(OPEN_MODE_RW);
+ return LocalFolder.this.localStore.getMessages(
+ listener,
+ LocalFolder.this,
+ "SELECT " + LocalStore.GET_MESSAGES_COLS +
+ "FROM messages " +
+ "LEFT JOIN threads ON (threads.message_id = messages.id) " +
+ "WHERE (empty IS NULL OR empty != 1) AND " +
+ (includeDeleted ? "" : "deleted = 0 AND ") +
+ "folder_id = ? ORDER BY date DESC",
+ new String[] { Long.toString(mFolderId) }
+ );
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
+ throws MessagingException {
+ open(OPEN_MODE_RW);
+ if (uids == null) {
+ return getMessages(listener);
+ }
+ ArrayList messages = new ArrayList();
+ for (String uid : uids) {
+ Message message = getMessage(uid);
+ if (message != null) {
+ messages.add(message);
+ }
+ }
+ return messages.toArray(LocalStore.EMPTY_MESSAGE_ARRAY);
+ }
+
+ @Override
+ public Map copyMessages(Message[] msgs, Folder folder) throws MessagingException {
+ if (!(folder instanceof LocalFolder)) {
+ throw new MessagingException("copyMessages called with incorrect Folder");
+ }
+ return ((LocalFolder) folder).appendMessages(msgs, true);
+ }
+
+ @Override
+ public Map moveMessages(final Message[] msgs, final Folder destFolder) throws MessagingException {
+ if (!(destFolder instanceof LocalFolder)) {
+ throw new MessagingException("moveMessages called with non-LocalFolder");
+ }
+
+ final LocalFolder lDestFolder = (LocalFolder)destFolder;
+
+ final Map uidMap = new HashMap();
+
+ try {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ lDestFolder.open(OPEN_MODE_RW);
+ for (Message message : msgs) {
+ LocalMessage lMessage = (LocalMessage)message;
+
+ String oldUID = message.getUid();
+
+ if (K9.DEBUG) {
+ Log.d(K9.LOG_TAG, "Updating folder_id to " + lDestFolder.getId() + " for message with UID "
+ + message.getUid() + ", id " + lMessage.getId() + " currently in folder " + getName());
+ }
+
+ String newUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString();
+ message.setUid(newUid);
+
+ uidMap.put(oldUID, newUid);
+
+ // Message threading in the target folder
+ ThreadInfo threadInfo = lDestFolder.doMessageThreading(db, message);
+
+ /*
+ * "Move" the message into the new folder
+ */
+ long msgId = lMessage.getId();
+ String[] idArg = new String[] { Long.toString(msgId) };
+
+ ContentValues cv = new ContentValues();
+ cv.put("folder_id", lDestFolder.getId());
+ cv.put("uid", newUid);
+
+ db.update("messages", cv, "id = ?", idArg);
+
+ // Create/update entry in 'threads' table for the message in the
+ // target folder
+ cv.clear();
+ cv.put("message_id", msgId);
+ if (threadInfo.threadId == -1) {
+ if (threadInfo.rootId != -1) {
+ cv.put("root", threadInfo.rootId);
+ }
+
+ if (threadInfo.parentId != -1) {
+ cv.put("parent", threadInfo.parentId);
+ }
+
+ db.insert("threads", null, cv);
+ } else {
+ db.update("threads", cv, "id = ?",
+ new String[] { Long.toString(threadInfo.threadId) });
+ }
+
+ /*
+ * Add a placeholder message so we won't download the original
+ * message again if we synchronize before the remote move is
+ * complete.
+ */
+
+ // We need to open this folder to get the folder id
+ open(OPEN_MODE_RW);
+
+ cv.clear();
+ cv.put("uid", oldUID);
+ cv.putNull("flags");
+ cv.put("read", 1);
+ cv.put("deleted", 1);
+ cv.put("folder_id", mFolderId);
+ cv.put("empty", 0);
+
+ String messageId = message.getMessageId();
+ if (messageId != null) {
+ cv.put("message_id", messageId);
+ }
+
+ final long newId;
+ if (threadInfo.msgId != -1) {
+ // There already existed an empty message in the target folder.
+ // Let's use it as placeholder.
+
+ newId = threadInfo.msgId;
+
+ db.update("messages", cv, "id = ?",
+ new String[] { Long.toString(newId) });
+ } else {
+ newId = db.insert("messages", null, cv);
+ }
+
+ /*
+ * Update old entry in 'threads' table to point to the newly
+ * created placeholder.
+ */
+
+ cv.clear();
+ cv.put("message_id", newId);
+ db.update("threads", cv, "id = ?",
+ new String[] { Long.toString(lMessage.getThreadId()) });
+ }
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ return null;
+ }
+ });
+
+ this.localStore.notifyChange();
+
+ return uidMap;
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+
+ }
+
+ /**
+ * Convenience transaction wrapper for storing a message and set it as fully downloaded. Implemented mainly to speed up DB transaction commit.
+ *
+ * @param message Message to store. Never null.
+ * @param runnable What to do before setting {@link Flag#X_DOWNLOADED_FULL}. Never null.
+ * @return The local version of the message. Never null.
+ * @throws MessagingException
+ */
+ public Message storeSmallMessage(final Message message, final Runnable runnable) throws MessagingException {
+ return this.localStore.database.execute(true, new DbCallback() {
+ @Override
+ public Message doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ appendMessages(new Message[] { message });
+ final String uid = message.getUid();
+ final Message result = getMessage(uid);
+ runnable.run();
+ // Set a flag indicating this message has now be fully downloaded
+ result.setFlag(Flag.X_DOWNLOADED_FULL, true);
+ return result;
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+ });
+ }
+
+ /**
+ * The method differs slightly from the contract; If an incoming message already has a uid
+ * assigned and it matches the uid of an existing message then this message will replace the
+ * old message. It is implemented as a delete/insert. This functionality is used in saving
+ * of drafts and re-synchronization of updated server messages.
+ *
+ * NOTE that although this method is located in the LocalStore class, it is not guaranteed
+ * that the messages supplied as parameters are actually {@link LocalMessage} instances (in
+ * fact, in most cases, they are not). Therefore, if you want to make local changes only to a
+ * message, retrieve the appropriate local message instance first (if it already exists).
+ */
+ @Override
+ public Map appendMessages(Message[] messages) throws MessagingException {
+ return appendMessages(messages, false);
+ }
+
+ public void destroyMessages(final Message[] messages) {
+ try {
+ this.localStore.database.execute(true, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ for (Message message : messages) {
+ try {
+ message.destroy();
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+ return null;
+ }
+ });
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+
+ private ThreadInfo getThreadInfo(SQLiteDatabase db, String messageId, boolean onlyEmpty) {
+ String sql = "SELECT t.id, t.message_id, t.root, t.parent " +
+ "FROM messages m " +
+ "LEFT JOIN threads t ON (t.message_id = m.id) " +
+ "WHERE m.folder_id = ? AND m.message_id = ? " +
+ ((onlyEmpty) ? "AND m.empty = 1 " : "") +
+ "ORDER BY m.id LIMIT 1";
+ String[] selectionArgs = { Long.toString(mFolderId), messageId };
+ Cursor cursor = db.rawQuery(sql, selectionArgs);
+
+ if (cursor != null) {
+ try {
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ long threadId = cursor.getLong(0);
+ long msgId = cursor.getLong(1);
+ long rootId = (cursor.isNull(2)) ? -1 : cursor.getLong(2);
+ long parentId = (cursor.isNull(3)) ? -1 : cursor.getLong(3);
+
+ return new ThreadInfo(threadId, msgId, messageId, rootId, parentId);
+ }
+ } finally {
+ cursor.close();
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * The method differs slightly from the contract; If an incoming message already has a uid
+ * assigned and it matches the uid of an existing message then this message will replace
+ * the old message. This functionality is used in saving of drafts and re-synchronization
+ * of updated server messages.
+ *
+ * NOTE that although this method is located in the LocalStore class, it is not guaranteed
+ * that the messages supplied as parameters are actually {@link LocalMessage} instances (in
+ * fact, in most cases, they are not). Therefore, if you want to make local changes only to a
+ * message, retrieve the appropriate local message instance first (if it already exists).
+ * @param messages
+ * @param copy
+ * @return Map uidMap of srcUids -> destUids
+ */
+ private Map appendMessages(final Message[] messages, final boolean copy) throws MessagingException {
+ open(OPEN_MODE_RW);
+ try {
+ final Map uidMap = new HashMap();
+ this.localStore.database.execute(true, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ for (Message message : messages) {
+ if (!(message instanceof MimeMessage)) {
+ throw new Error("LocalStore can only store Messages that extend MimeMessage");
+ }
+
+ long oldMessageId = -1;
+ String uid = message.getUid();
+ if (uid == null || copy) {
+ /*
+ * Create a new message in the database
+ */
+ String randomLocalUid = K9.LOCAL_UID_PREFIX +
+ UUID.randomUUID().toString();
+
+ if (copy) {
+ // Save mapping: source UID -> target UID
+ uidMap.put(uid, randomLocalUid);
+ } else {
+ // Modify the Message instance to reference the new UID
+ message.setUid(randomLocalUid);
+ }
+
+ // The message will be saved with the newly generated UID
+ uid = randomLocalUid;
+ } else {
+ /*
+ * Replace an existing message in the database
+ */
+ LocalMessage oldMessage = getMessage(uid);
+
+ if (oldMessage != null) {
+ oldMessageId = oldMessage.getId();
+ }
+
+ deleteAttachments(message.getUid());
+ }
+
+ long rootId = -1;
+ long parentId = -1;
+
+ if (oldMessageId == -1) {
+ // This is a new message. Do the message threading.
+ ThreadInfo threadInfo = doMessageThreading(db, message);
+ oldMessageId = threadInfo.msgId;
+ rootId = threadInfo.rootId;
+ parentId = threadInfo.parentId;
+ }
+
+ boolean isDraft = (message.getHeader(K9.IDENTITY_HEADER) != null);
+
+ List attachments;
+ String text;
+ String html;
+ if (isDraft) {
+ // Don't modify the text/plain or text/html part of our own
+ // draft messages because this will cause the values stored in
+ // the identity header to be wrong.
+ ViewableContainer container =
+ MimeUtility.extractPartsFromDraft(message);
+
+ text = container.text;
+ html = container.html;
+ attachments = container.attachments;
+ } else {
+ ViewableContainer container =
+ MimeUtility.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
+
+ attachments = container.attachments;
+ text = container.text;
+ html = HtmlConverter.convertEmoji2Img(container.html);
+ }
+
+ String preview = Message.calculateContentPreview(text);
+
+ try {
+ ContentValues cv = new ContentValues();
+ cv.put("uid", uid);
+ cv.put("subject", message.getSubject());
+ cv.put("sender_list", Address.pack(message.getFrom()));
+ cv.put("date", message.getSentDate() == null
+ ? System.currentTimeMillis() : message.getSentDate().getTime());
+ cv.put("flags", LocalFolder.this.localStore.serializeFlags(message.getFlags()));
+ cv.put("deleted", message.isSet(Flag.DELETED) ? 1 : 0);
+ cv.put("read", message.isSet(Flag.SEEN) ? 1 : 0);
+ cv.put("flagged", message.isSet(Flag.FLAGGED) ? 1 : 0);
+ cv.put("answered", message.isSet(Flag.ANSWERED) ? 1 : 0);
+ cv.put("forwarded", message.isSet(Flag.FORWARDED) ? 1 : 0);
+ cv.put("folder_id", mFolderId);
+ cv.put("to_list", Address.pack(message.getRecipients(RecipientType.TO)));
+ cv.put("cc_list", Address.pack(message.getRecipients(RecipientType.CC)));
+ cv.put("bcc_list", Address.pack(message.getRecipients(RecipientType.BCC)));
+ cv.put("html_content", html.length() > 0 ? html : null);
+ cv.put("text_content", text.length() > 0 ? text : null);
+ cv.put("preview", preview.length() > 0 ? preview : null);
+ cv.put("reply_to_list", Address.pack(message.getReplyTo()));
+ cv.put("attachment_count", attachments.size());
+ cv.put("internal_date", message.getInternalDate() == null
+ ? System.currentTimeMillis() : message.getInternalDate().getTime());
+ cv.put("mime_type", message.getMimeType());
+ cv.put("empty", 0);
+
+ String messageId = message.getMessageId();
+ if (messageId != null) {
+ cv.put("message_id", messageId);
+ }
+
+ long msgId;
+
+ if (oldMessageId == -1) {
+ msgId = db.insert("messages", "uid", cv);
+
+ // Create entry in 'threads' table
+ cv.clear();
+ cv.put("message_id", msgId);
+
+ if (rootId != -1) {
+ cv.put("root", rootId);
+ }
+ if (parentId != -1) {
+ cv.put("parent", parentId);
+ }
+
+ db.insert("threads", null, cv);
+ } else {
+ db.update("messages", cv, "id = ?", new String[] { Long.toString(oldMessageId) });
+ msgId = oldMessageId;
+ }
+
+ for (Part attachment : attachments) {
+ saveAttachment(msgId, attachment, copy);
+ }
+ saveHeaders(msgId, (MimeMessage)message);
+ } catch (Exception e) {
+ throw new MessagingException("Error appending message", e);
+ }
+ }
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ return null;
+ }
+ });
+
+ this.localStore.notifyChange();
+
+ return uidMap;
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ /**
+ * Update the given message in the LocalStore without first deleting the existing
+ * message (contrast with appendMessages). This method is used to store changes
+ * to the given message while updating attachments and not removing existing
+ * attachment data.
+ * TODO In the future this method should be combined with appendMessages since the Message
+ * contains enough data to decide what to do.
+ * @param message
+ * @throws MessagingException
+ */
+ public void updateMessage(final LocalMessage message) throws MessagingException {
+ open(OPEN_MODE_RW);
+ try {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ message.buildMimeRepresentation();
+
+ ViewableContainer container =
+ MimeUtility.extractTextAndAttachments(LocalFolder.this.localStore.mApplication, message);
+
+ List attachments = container.attachments;
+ String text = container.text;
+ String html = HtmlConverter.convertEmoji2Img(container.html);
+
+ String preview = Message.calculateContentPreview(text);
+
+ try {
+ db.execSQL("UPDATE messages SET "
+ + "uid = ?, subject = ?, sender_list = ?, date = ?, flags = ?, "
+ + "folder_id = ?, to_list = ?, cc_list = ?, bcc_list = ?, "
+ + "html_content = ?, text_content = ?, preview = ?, reply_to_list = ?, "
+ + "attachment_count = ?, read = ?, flagged = ?, answered = ?, forwarded = ? "
+ + "WHERE id = ?",
+ new Object[] {
+ message.getUid(),
+ message.getSubject(),
+ Address.pack(message.getFrom()),
+ message.getSentDate() == null ? System
+ .currentTimeMillis() : message.getSentDate()
+ .getTime(),
+ LocalFolder.this.localStore.serializeFlags(message.getFlags()),
+ mFolderId,
+ Address.pack(message
+ .getRecipients(RecipientType.TO)),
+ Address.pack(message
+ .getRecipients(RecipientType.CC)),
+ Address.pack(message
+ .getRecipients(RecipientType.BCC)),
+ html.length() > 0 ? html : null,
+ text.length() > 0 ? text : null,
+ preview.length() > 0 ? preview : null,
+ Address.pack(message.getReplyTo()),
+ attachments.size(),
+ message.isSet(Flag.SEEN) ? 1 : 0,
+ message.isSet(Flag.FLAGGED) ? 1 : 0,
+ message.isSet(Flag.ANSWERED) ? 1 : 0,
+ message.isSet(Flag.FORWARDED) ? 1 : 0,
+ message.mId
+ });
+
+ for (int i = 0, count = attachments.size(); i < count; i++) {
+ Part attachment = attachments.get(i);
+ saveAttachment(message.mId, attachment, false);
+ }
+ saveHeaders(message.getId(), message);
+ } catch (Exception e) {
+ throw new MessagingException("Error appending message", e);
+ }
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ return null;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+
+ this.localStore.notifyChange();
+ }
+
+ /**
+ * Save the headers of the given message. Note that the message is not
+ * necessarily a {@link LocalMessage} instance.
+ * @param id
+ * @param message
+ * @throws com.fsck.k9.mail.MessagingException
+ */
+ private void saveHeaders(final long id, final MimeMessage message) throws MessagingException {
+ this.localStore.database.execute(true, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+
+ deleteHeaders(id);
+ for (String name : message.getHeaderNames()) {
+ String[] values = message.getHeader(name);
+ for (String value : values) {
+ ContentValues cv = new ContentValues();
+ cv.put("message_id", id);
+ cv.put("name", name);
+ cv.put("value", value);
+ db.insert("headers", "name", cv);
+ }
+ }
+
+ // Remember that all headers for this message have been saved, so it is
+ // not necessary to download them again in case the user wants to see all headers.
+ List appendedFlags = new ArrayList();
+ appendedFlags.addAll(Arrays.asList(message.getFlags()));
+ appendedFlags.add(Flag.X_GOT_ALL_HEADERS);
+
+ db.execSQL("UPDATE messages " + "SET flags = ? " + " WHERE id = ?",
+ new Object[]
+ { LocalFolder.this.localStore.serializeFlags(appendedFlags.toArray(LocalStore.EMPTY_FLAG_ARRAY)), id });
+
+ return null;
+ }
+ });
+ }
+
+ void deleteHeaders(final long id) throws UnavailableStorageException {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ db.execSQL("DELETE FROM headers WHERE message_id = ?", new Object[]
+ { id });
+ return null;
+ }
+ });
+ }
+
+ /**
+ * @param messageId
+ * @param attachment
+ * @param saveAsNew
+ * @throws IOException
+ * @throws MessagingException
+ */
+ private void saveAttachment(final long messageId, final Part attachment, final boolean saveAsNew)
+ throws IOException, MessagingException {
+ try {
+ this.localStore.database.execute(true, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ long attachmentId = -1;
+ Uri contentUri = null;
+ int size = -1;
+ File tempAttachmentFile = null;
+
+ if ((!saveAsNew) && (attachment instanceof LocalAttachmentBodyPart)) {
+ attachmentId = ((LocalAttachmentBodyPart) attachment).getAttachmentId();
+ }
+
+ final File attachmentDirectory = StorageManager.getInstance(LocalFolder.this.localStore.mApplication).getAttachmentDirectory(LocalFolder.this.localStore.uUid, LocalFolder.this.localStore.database.getStorageProviderId());
+ if (attachment.getBody() != null) {
+ Body body = attachment.getBody();
+ if (body instanceof LocalAttachmentBody) {
+ contentUri = ((LocalAttachmentBody) body).getContentUri();
+ } else if (body instanceof Message) {
+ // It's a message, so use Message.writeTo() to output the
+ // message including all children.
+ Message message = (Message) body;
+ tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
+ FileOutputStream out = new FileOutputStream(tempAttachmentFile);
+ try {
+ message.writeTo(out);
+ } finally {
+ out.close();
+ }
+ size = (int) (tempAttachmentFile.length() & 0x7FFFFFFFL);
+ } else {
+ /*
+ * If the attachment has a body we're expected to save it into the local store
+ * so we copy the data into a cached attachment file.
+ */
+ InputStream in = attachment.getBody().getInputStream();
+ try {
+ tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
+ FileOutputStream out = new FileOutputStream(tempAttachmentFile);
+ try {
+ size = IOUtils.copy(in, out);
+ } finally {
+ out.close();
+ }
+ } finally {
+ try { in.close(); } catch (Throwable ignore) {}
+ }
+ }
+ }
+
+ if (size == -1) {
+ /*
+ * If the attachment is not yet downloaded see if we can pull a size
+ * off the Content-Disposition.
+ */
+ String disposition = attachment.getDisposition();
+ if (disposition != null) {
+ String sizeParam = MimeUtility.getHeaderParameter(disposition, "size");
+ if (sizeParam != null) {
+ try {
+ size = Integer.parseInt(sizeParam);
+ } catch (NumberFormatException e) { /* Ignore */ }
+ }
+ }
+ }
+ if (size == -1) {
+ size = 0;
+ }
+
+ String storeData =
+ Utility.combine(attachment.getHeader(
+ MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA), ',');
+
+ String name = MimeUtility.getHeaderParameter(attachment.getContentType(), "name");
+ String contentId = MimeUtility.getHeaderParameter(attachment.getContentId(), null);
+
+ String contentDisposition = MimeUtility.unfoldAndDecode(attachment.getDisposition());
+ String dispositionType = contentDisposition;
+
+ if (dispositionType != null) {
+ int pos = dispositionType.indexOf(';');
+ if (pos != -1) {
+ // extract the disposition-type, "attachment", "inline" or extension-token (see the RFC 2183)
+ dispositionType = dispositionType.substring(0, pos);
+ }
+ }
+
+ if (name == null && contentDisposition != null) {
+ name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
+ }
+ if (attachmentId == -1) {
+ ContentValues cv = new ContentValues();
+ cv.put("message_id", messageId);
+ cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
+ cv.put("store_data", storeData);
+ cv.put("size", size);
+ cv.put("name", name);
+ cv.put("mime_type", attachment.getMimeType());
+ cv.put("content_id", contentId);
+ cv.put("content_disposition", dispositionType);
+
+ attachmentId = db.insert("attachments", "message_id", cv);
+ } else {
+ ContentValues cv = new ContentValues();
+ cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
+ cv.put("size", size);
+ db.update("attachments", cv, "id = ?", new String[]
+ { Long.toString(attachmentId) });
+ }
+
+ if (attachmentId != -1 && tempAttachmentFile != null) {
+ File attachmentFile = new File(attachmentDirectory, Long.toString(attachmentId));
+ tempAttachmentFile.renameTo(attachmentFile);
+ contentUri = AttachmentProvider.getAttachmentUri(
+ mAccount,
+ attachmentId);
+ if (MimeUtil.isMessage(attachment.getMimeType())) {
+ attachment.setBody(new LocalAttachmentMessageBody(
+ contentUri, LocalFolder.this.localStore.mApplication));
+ } else {
+ attachment.setBody(new LocalAttachmentBody(
+ contentUri, LocalFolder.this.localStore.mApplication));
+ }
+ ContentValues cv = new ContentValues();
+ cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
+ db.update("attachments", cv, "id = ?", new String[]
+ { Long.toString(attachmentId) });
+ }
+
+ /* The message has attachment with Content-ID */
+ if (contentId != null && contentUri != null) {
+ Cursor cursor = db.query("messages", new String[]
+ { "html_content" }, "id = ?", new String[]
+ { Long.toString(messageId) }, null, null, null);
+ try {
+ if (cursor.moveToNext()) {
+ String htmlContent = cursor.getString(0);
+
+ if (htmlContent != null) {
+ String newHtmlContent = htmlContent.replaceAll(
+ Pattern.quote("cid:" + contentId),
+ contentUri.toString());
+
+ ContentValues cv = new ContentValues();
+ cv.put("html_content", newHtmlContent);
+ db.update("messages", cv, "id = ?", new String[]
+ { Long.toString(messageId) });
+ }
+ }
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ }
+
+ if (attachmentId != -1 && attachment instanceof LocalAttachmentBodyPart) {
+ ((LocalAttachmentBodyPart) attachment).setAttachmentId(attachmentId);
+ }
+ return null;
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ } catch (IOException e) {
+ throw new WrappedException(e);
+ }
+ }
+ });
+ } catch (WrappedException e) {
+ final Throwable cause = e.getCause();
+ if (cause instanceof IOException) {
+ throw (IOException) cause;
+ }
+
+ throw (MessagingException) cause;
+ }
+ }
+
+ /**
+ * Changes the stored uid of the given message (using it's internal id as a key) to
+ * the uid in the message.
+ * @param message
+ * @throws com.fsck.k9.mail.MessagingException
+ */
+ public void changeUid(final LocalMessage message) throws MessagingException {
+ open(OPEN_MODE_RW);
+ final ContentValues cv = new ContentValues();
+ cv.put("uid", message.getUid());
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ db.update("messages", cv, "id = ?", new String[]
+ { Long.toString(message.mId) });
+ return null;
+ }
+ });
+
+ //TODO: remove this once the UI code exclusively uses the database id
+ this.localStore.notifyChange();
+ }
+
+ @Override
+ public void setFlags(final Message[] messages, final Flag[] flags, final boolean value)
+ throws MessagingException {
+ open(OPEN_MODE_RW);
+
+ // Use one transaction to set all flags
+ try {
+ this.localStore.database.execute(true, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
+ UnavailableStorageException {
+
+ for (Message message : messages) {
+ try {
+ message.setFlags(flags, value);
+ } catch (MessagingException e) {
+ Log.e(K9.LOG_TAG, "Something went wrong while setting flag", e);
+ }
+ }
+
+ return null;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public void setFlags(Flag[] flags, boolean value)
+ throws MessagingException {
+ open(OPEN_MODE_RW);
+ for (Message message : getMessages(null)) {
+ message.setFlags(flags, value);
+ }
+ }
+
+ @Override
+ public String getUidFromMessageId(Message message) throws MessagingException {
+ throw new MessagingException("Cannot call getUidFromMessageId on LocalFolder");
+ }
+
+ public void clearMessagesOlderThan(long cutoff) throws MessagingException {
+ open(OPEN_MODE_RO);
+
+ Message[] messages = this.localStore.getMessages(
+ null,
+ this,
+ "SELECT " + LocalStore.GET_MESSAGES_COLS +
+ "FROM messages " +
+ "LEFT JOIN threads ON (threads.message_id = messages.id) " +
+ "WHERE (empty IS NULL OR empty != 1) AND " +
+ "(folder_id = ? and date < ?)",
+ new String[] {
+ Long.toString(mFolderId), Long.toString(cutoff)
+ });
+
+ for (Message message : messages) {
+ message.destroy();
+ }
+
+ this.localStore.notifyChange();
+ }
+
+ public void clearAllMessages() throws MessagingException {
+ final String[] folderIdArg = new String[] { Long.toString(mFolderId) };
+
+ open(OPEN_MODE_RO);
+
+ try {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
+ try {
+ // Get UIDs for all messages to delete
+ Cursor cursor = db.query("messages", new String[] { "uid" },
+ "folder_id = ? AND (empty IS NULL OR empty != 1)",
+ folderIdArg, null, null, null);
+
+ try {
+ // Delete attachments of these messages
+ while (cursor.moveToNext()) {
+ deleteAttachments(cursor.getString(0));
+ }
+ } finally {
+ cursor.close();
+ }
+
+ // Delete entries in 'threads' and 'messages'
+ db.execSQL("DELETE FROM threads WHERE message_id IN " +
+ "(SELECT id FROM messages WHERE folder_id = ?)", folderIdArg);
+ db.execSQL("DELETE FROM messages WHERE folder_id = ?", folderIdArg);
+
+ return null;
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+
+ this.localStore.notifyChange();
+
+ setPushState(null);
+ setLastPush(0);
+ setLastChecked(0);
+ setVisibleLimit(mAccount.getDisplayCount());
+ }
+
+ @Override
+ public void delete(final boolean recurse) throws MessagingException {
+ try {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ try {
+ // We need to open the folder first to make sure we've got it's id
+ open(OPEN_MODE_RO);
+ Message[] messages = getMessages(null);
+ for (Message message : messages) {
+ deleteAttachments(message.getUid());
+ }
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+ db.execSQL("DELETE FROM folders WHERE id = ?", new Object[]
+ { Long.toString(mFolderId), });
+ return null;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof LocalFolder) {
+ return ((LocalFolder)o).mName.equals(mName);
+ }
+ return super.equals(o);
+ }
+
+ @Override
+ public int hashCode() {
+ return mName.hashCode();
+ }
+
+ void deleteAttachments(final long messageId) throws MessagingException {
+ open(OPEN_MODE_RW);
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ Cursor attachmentsCursor = null;
+ try {
+ String accountUuid = mAccount.getUuid();
+ Context context = LocalFolder.this.localStore.mApplication;
+
+ // Get attachment IDs
+ String[] whereArgs = new String[] { Long.toString(messageId) };
+ attachmentsCursor = db.query("attachments", new String[] { "id" },
+ "message_id = ?", whereArgs, null, null, null);
+
+ final File attachmentDirectory = StorageManager.getInstance(LocalFolder.this.localStore.mApplication)
+ .getAttachmentDirectory(LocalFolder.this.localStore.uUid, LocalFolder.this.localStore.database.getStorageProviderId());
+
+ while (attachmentsCursor.moveToNext()) {
+ String attachmentId = Long.toString(attachmentsCursor.getLong(0));
+ try {
+ // Delete stored attachment
+ File file = new File(attachmentDirectory, attachmentId);
+ if (file.exists()) {
+ file.delete();
+ }
+
+ // Delete thumbnail file
+ AttachmentProvider.deleteThumbnail(context, accountUuid,
+ attachmentId);
+ } catch (Exception e) { /* ignore */ }
+ }
+
+ // Delete attachment metadata from the database
+ db.delete("attachments", "message_id = ?", whereArgs);
+ } finally {
+ Utility.closeQuietly(attachmentsCursor);
+ }
+ return null;
+ }
+ });
+ }
+
+ private void deleteAttachments(final String uid) throws MessagingException {
+ open(OPEN_MODE_RW);
+ try {
+ this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
+ Cursor messagesCursor = null;
+ try {
+ messagesCursor = db.query("messages", new String[]
+ { "id" }, "folder_id = ? AND uid = ?", new String[]
+ { Long.toString(mFolderId), uid }, null, null, null);
+ while (messagesCursor.moveToNext()) {
+ long messageId = messagesCursor.getLong(0);
+ deleteAttachments(messageId);
+
+ }
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ } finally {
+ Utility.closeQuietly(messagesCursor);
+ }
+ return null;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+
+ @Override
+ public boolean isInTopGroup() {
+ return mInTopGroup;
+ }
+
+ public void setInTopGroup(boolean inTopGroup) throws MessagingException {
+ mInTopGroup = inTopGroup;
+ updateFolderColumn("top_group", mInTopGroup ? 1 : 0);
+ }
+
+ public Integer getLastUid() {
+ return mLastUid;
+ }
+
+ /**
+ *
Fetches the most recent numeric UID value in this folder. This is used by
+ * {@link com.fsck.k9.controller.MessagingController#shouldNotifyForMessage} to see if messages being
+ * fetched are new and unread. Messages are "new" if they have a UID higher than the most recent UID prior
+ * to synchronization.
+ *
+ *
This only works for protocols with numeric UIDs (like IMAP). For protocols with
+ * alphanumeric UIDs (like POP), this method quietly fails and shouldNotifyForMessage() will
+ * always notify for unread messages.
+ *
+ *
Once Issue 1072 has been fixed, this method and shouldNotifyForMessage() should be
+ * updated to use internal dates rather than UIDs to determine new-ness. While this doesn't
+ * solve things for POP (which doesn't have internal dates), we can likely use this as a
+ * framework to examine send date in lieu of internal date.
+ * @throws MessagingException
+ */
+ public void updateLastUid() throws MessagingException {
+ Integer lastUid = this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Integer doDbWork(final SQLiteDatabase db) {
+ Cursor cursor = null;
+ try {
+ open(OPEN_MODE_RO);
+ cursor = db.rawQuery("SELECT MAX(uid) FROM messages WHERE folder_id=?", new String[] { Long.toString(mFolderId) });
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ return cursor.getInt(0);
+ }
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "Unable to updateLastUid: ", e);
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ return null;
+ }
+ });
+ if (K9.DEBUG)
+ Log.d(K9.LOG_TAG, "Updated last UID for folder " + mName + " to " + lastUid);
+ mLastUid = lastUid;
+ }
+
+ public Long getOldestMessageDate() throws MessagingException {
+ return this.localStore.database.execute(false, new DbCallback() {
+ @Override
+ public Long doDbWork(final SQLiteDatabase db) {
+ Cursor cursor = null;
+ try {
+ open(OPEN_MODE_RO);
+ cursor = db.rawQuery("SELECT MIN(date) FROM messages WHERE folder_id=?", new String[] { Long.toString(mFolderId) });
+ if (cursor.getCount() > 0) {
+ cursor.moveToFirst();
+ return cursor.getLong(0);
+ }
+ } catch (Exception e) {
+ Log.e(K9.LOG_TAG, "Unable to fetch oldest message date: ", e);
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+ return null;
+ }
+ });
+ }
+
+ private ThreadInfo doMessageThreading(SQLiteDatabase db, Message message)
+ throws MessagingException {
+ long rootId = -1;
+ long parentId = -1;
+
+ String messageId = message.getMessageId();
+
+ // If there's already an empty message in the database, update that
+ ThreadInfo msgThreadInfo = getThreadInfo(db, messageId, true);
+
+ // Get the message IDs from the "References" header line
+ String[] referencesArray = message.getHeader("References");
+ List messageIds = null;
+ if (referencesArray != null && referencesArray.length > 0) {
+ messageIds = Utility.extractMessageIds(referencesArray[0]);
+ }
+
+ // Append the first message ID from the "In-Reply-To" header line
+ String[] inReplyToArray = message.getHeader("In-Reply-To");
+ String inReplyTo = null;
+ if (inReplyToArray != null && inReplyToArray.length > 0) {
+ inReplyTo = Utility.extractMessageId(inReplyToArray[0]);
+ if (inReplyTo != null) {
+ if (messageIds == null) {
+ messageIds = new ArrayList(1);
+ messageIds.add(inReplyTo);
+ } else if (!messageIds.contains(inReplyTo)) {
+ messageIds.add(inReplyTo);
+ }
+ }
+ }
+
+ if (messageIds == null) {
+ // This is not a reply, nothing to do for us.
+ return (msgThreadInfo != null) ?
+ msgThreadInfo : new ThreadInfo(-1, -1, messageId, -1, -1);
+ }
+
+ for (String reference : messageIds) {
+ ThreadInfo threadInfo = getThreadInfo(db, reference, false);
+
+ if (threadInfo == null) {
+ // Create placeholder message in 'messages' table
+ ContentValues cv = new ContentValues();
+ cv.put("message_id", reference);
+ cv.put("folder_id", mFolderId);
+ cv.put("empty", 1);
+
+ long newMsgId = db.insert("messages", null, cv);
+
+ // Create entry in 'threads' table
+ cv.clear();
+ cv.put("message_id", newMsgId);
+ if (rootId != -1) {
+ cv.put("root", rootId);
+ }
+ if (parentId != -1) {
+ cv.put("parent", parentId);
+ }
+
+ parentId = db.insert("threads", null, cv);
+ if (rootId == -1) {
+ rootId = parentId;
+ }
+ } else {
+ if (rootId != -1 && threadInfo.rootId == -1 && rootId != threadInfo.threadId) {
+ // We found an existing root container that is not
+ // the root of our current path (References).
+ // Connect it to the current parent.
+
+ // Let all children know who's the new root
+ ContentValues cv = new ContentValues();
+ cv.put("root", rootId);
+ db.update("threads", cv, "root = ?",
+ new String[] { Long.toString(threadInfo.threadId) });
+
+ // Connect the message to the current parent
+ cv.put("parent", parentId);
+ db.update("threads", cv, "id = ?",
+ new String[] { Long.toString(threadInfo.threadId) });
+ } else {
+ rootId = (threadInfo.rootId == -1) ?
+ threadInfo.threadId : threadInfo.rootId;
+ }
+ parentId = threadInfo.threadId;
+ }
+ }
+
+ //TODO: set in-reply-to "link" even if one already exists
+
+ long threadId;
+ long msgId;
+ if (msgThreadInfo != null) {
+ threadId = msgThreadInfo.threadId;
+ msgId = msgThreadInfo.msgId;
+ } else {
+ threadId = -1;
+ msgId = -1;
+ }
+
+ return new ThreadInfo(threadId, msgId, messageId, rootId, parentId);
+ }
+
+ public List extractNewMessages(final List messages)
+ throws MessagingException {
+
+ try {
+ return this.localStore.database.execute(false, new DbCallback>() {
+ @Override
+ public List doDbWork(final SQLiteDatabase db) throws WrappedException {
+ try {
+ open(OPEN_MODE_RW);
+ } catch (MessagingException e) {
+ throw new WrappedException(e);
+ }
+
+ List result = new ArrayList();
+
+ List selectionArgs = new ArrayList();
+ Set existingMessages = new HashSet();
+ int start = 0;
+
+ while (start < messages.size()) {
+ StringBuilder selection = new StringBuilder();
+
+ selection.append("folder_id = ? AND UID IN (");
+ selectionArgs.add(Long.toString(mFolderId));
+
+ int count = Math.min(messages.size() - start, LocalStore.UID_CHECK_BATCH_SIZE);
+
+ for (int i = start, end = start + count; i < end; i++) {
+ if (i > start) {
+ selection.append(",?");
+ } else {
+ selection.append("?");
+ }
+
+ selectionArgs.add(messages.get(i).getUid());
+ }
+
+ selection.append(")");
+
+ Cursor cursor = db.query("messages", LocalStore.UID_CHECK_PROJECTION,
+ selection.toString(), selectionArgs.toArray(LocalStore.EMPTY_STRING_ARRAY),
+ null, null, null);
+
+ try {
+ while (cursor.moveToNext()) {
+ String uid = cursor.getString(0);
+ existingMessages.add(uid);
+ }
+ } finally {
+ Utility.closeQuietly(cursor);
+ }
+
+ for (int i = start, end = start + count; i < end; i++) {
+ Message message = messages.get(i);
+ if (!existingMessages.contains(message.getUid())) {
+ result.add(message);
+ }
+ }
+
+ existingMessages.clear();
+ selectionArgs.clear();
+ start += count;
+ }
+
+ return result;
+ }
+ });
+ } catch (WrappedException e) {
+ throw(MessagingException) e.getCause();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/store/local/LocalMessage.java b/src/com/fsck/k9/mail/store/local/LocalMessage.java
new file mode 100644
index 000000000..8f9dda8d3
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/LocalMessage.java
@@ -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() {
+ @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() {
+ @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() {
+ @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 messages = 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) 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 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;
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/store/local/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
index 327cf5388..7ede93e36 100644
--- a/src/com/fsck/k9/mail/store/local/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -2,28 +2,16 @@
package com.fsck.k9.mail.store.local;
import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
-import java.util.Set;
-import java.util.UUID;
-import java.util.regex.Pattern;
-import org.apache.commons.io.IOUtils;
-import org.apache.james.mime4j.util.MimeUtil;
import android.app.Application;
import android.content.ContentResolver;
@@ -40,31 +28,14 @@ import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
-import com.fsck.k9.Account.MessageFormat;
-import com.fsck.k9.activity.Search;
-import com.fsck.k9.controller.MessageRemovalListener;
import com.fsck.k9.controller.MessageRetrievalListener;
-import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility;
-import com.fsck.k9.mail.Address;
-import com.fsck.k9.mail.Body;
-import com.fsck.k9.mail.BodyPart;
-import com.fsck.k9.mail.FetchProfile;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
-import com.fsck.k9.mail.Message.RecipientType;
import com.fsck.k9.mail.MessagingException;
-import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.Store;
-import com.fsck.k9.mail.internet.MimeBodyPart;
-import com.fsck.k9.mail.internet.MimeHeader;
-import com.fsck.k9.mail.internet.MimeMessage;
-import com.fsck.k9.mail.internet.MimeMultipart;
-import com.fsck.k9.mail.internet.MimeUtility;
-import com.fsck.k9.mail.internet.MimeUtility.ViewableContainer;
-import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.store.LockableDatabase;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.UnavailableStorageException;
@@ -89,47 +60,47 @@ public class LocalStore extends Store implements Serializable {
private static final long serialVersionUID = -5142141896809423072L;
- private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
- private static final String[] EMPTY_STRING_ARRAY = new String[0];
- private static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
+ static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
+ static final String[] EMPTY_STRING_ARRAY = new String[0];
+ static final Flag[] EMPTY_FLAG_ARRAY = new Flag[0];
static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
/*
* a String containing the columns getMessages expects to work with
* in the correct order.
*/
- static private String GET_MESSAGES_COLS =
+ static String GET_MESSAGES_COLS =
"subject, sender_list, date, uid, flags, messages.id, to_list, cc_list, " +
"bcc_list, reply_to_list, attachment_count, internal_date, messages.message_id, " +
"folder_id, preview, threads.id, threads.root, deleted, read, flagged, answered, " +
"forwarded ";
- private static final String GET_FOLDER_COLS =
+ static final String GET_FOLDER_COLS =
"folders.id, name, visible_limit, last_updated, status, push_state, last_pushed, " +
"integrate, top_group, poll_class, push_class, display_class, notify_class";
- private static final int FOLDER_ID_INDEX = 0;
- private static final int FOLDER_NAME_INDEX = 1;
- private static final int FOLDER_VISIBLE_LIMIT_INDEX = 2;
- private static final int FOLDER_LAST_CHECKED_INDEX = 3;
- private static final int FOLDER_STATUS_INDEX = 4;
- private static final int FOLDER_PUSH_STATE_INDEX = 5;
- private static final int FOLDER_LAST_PUSHED_INDEX = 6;
- private static final int FOLDER_INTEGRATE_INDEX = 7;
- private static final int FOLDER_TOP_GROUP_INDEX = 8;
- private static final int FOLDER_SYNC_CLASS_INDEX = 9;
- private static final int FOLDER_PUSH_CLASS_INDEX = 10;
- private static final int FOLDER_DISPLAY_CLASS_INDEX = 11;
- private static final int FOLDER_NOTIFY_CLASS_INDEX = 12;
+ static final int FOLDER_ID_INDEX = 0;
+ static final int FOLDER_NAME_INDEX = 1;
+ static final int FOLDER_VISIBLE_LIMIT_INDEX = 2;
+ static final int FOLDER_LAST_CHECKED_INDEX = 3;
+ static final int FOLDER_STATUS_INDEX = 4;
+ static final int FOLDER_PUSH_STATE_INDEX = 5;
+ static final int FOLDER_LAST_PUSHED_INDEX = 6;
+ static final int FOLDER_INTEGRATE_INDEX = 7;
+ static final int FOLDER_TOP_GROUP_INDEX = 8;
+ static final int FOLDER_SYNC_CLASS_INDEX = 9;
+ static final int FOLDER_PUSH_CLASS_INDEX = 10;
+ static final int FOLDER_DISPLAY_CLASS_INDEX = 11;
+ static final int FOLDER_NOTIFY_CLASS_INDEX = 12;
- private static final String[] UID_CHECK_PROJECTION = { "uid" };
+ static final String[] UID_CHECK_PROJECTION = { "uid" };
/**
* Maximum number of UIDs to check for existence at once.
*
* @see LocalFolder#extractNewMessages(List)
*/
- private static final int UID_CHECK_BATCH_SIZE = 500;
+ static final int UID_CHECK_BATCH_SIZE = 500;
/**
* Maximum number of messages to perform flag updates on at once.
@@ -171,9 +142,9 @@ public class LocalStore extends Store implements Serializable {
protected String uUid = null;
- private final Application mApplication;
+ final Application mApplication;
- private LockableDatabase database;
+ LockableDatabase database;
private ContentResolver mContentResolver;
@@ -467,7 +438,7 @@ public class LocalStore extends Store implements Serializable {
try {
// If folder "OUTBOX" (old, v3.800 - v3.802) exists, rename it to
// "K9MAIL_INTERNAL_OUTBOX" (new)
- LocalFolder oldOutbox = new LocalFolder("OUTBOX");
+ LocalFolder oldOutbox = new LocalFolder(LocalStore.this, "OUTBOX");
if (oldOutbox.exists()) {
ContentValues cv = new ContentValues();
cv.put("name", Account.OUTBOX);
@@ -477,7 +448,7 @@ public class LocalStore extends Store implements Serializable {
// 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(localizedOutbox);
+ LocalFolder obsoleteOutbox = new LocalFolder(LocalStore.this, localizedOutbox);
if (obsoleteOutbox.exists()) {
// Get all messages from the localized outbox ...
Message[] messages = obsoleteOutbox.getMessages(null, false);
@@ -485,7 +456,7 @@ public class LocalStore extends Store implements Serializable {
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(mAccount.getDraftsFolderName());
+ LocalFolder drafts = new LocalFolder(LocalStore.this, mAccount.getDraftsFolderName());
obsoleteOutbox.moveMessages(messages, drafts);
}
@@ -890,11 +861,11 @@ public class LocalStore extends Store implements Serializable {
@Override
public LocalFolder getFolder(String name) {
- return new LocalFolder(name);
+ return new LocalFolder(this, name);
}
public LocalFolder getFolderById(long folderId) {
- return new LocalFolder(folderId);
+ return new LocalFolder(this, folderId);
}
// TODO this takes about 260-300ms, seems slow.
@@ -915,7 +886,7 @@ public class LocalStore extends Store implements Serializable {
continue;
}
String folderName = cursor.getString(FOLDER_NAME_INDEX);
- LocalFolder folder = new LocalFolder(folderName);
+ LocalFolder folder = new LocalFolder(LocalStore.this, folderName);
folder.open(cursor);
folders.add(folder);
@@ -1165,7 +1136,7 @@ public class LocalStore extends Store implements Serializable {
* Given a query string, actually do the query for the messages and
* call the MessageRetrievalListener for each one
*/
- private Message[] getMessages(
+ Message[] getMessages(
final MessageRetrievalListener listener,
final LocalFolder folder,
final String queryString, final String[] placeHolders
@@ -1180,7 +1151,7 @@ public class LocalStore extends Store implements Serializable {
cursor = db.rawQuery(queryString + " LIMIT 10", placeHolders);
while (cursor.moveToNext()) {
- LocalMessage message = new LocalMessage(null, folder);
+ LocalMessage message = new LocalMessage(LocalStore.this, null, folder);
message.populateFromGetMessageCursor(cursor);
messages.add(message);
@@ -1193,7 +1164,7 @@ public class LocalStore extends Store implements Serializable {
cursor = db.rawQuery(queryString + " LIMIT -1 OFFSET 10", placeHolders);
while (cursor.moveToNext()) {
- LocalMessage message = new LocalMessage(null, folder);
+ LocalMessage message = new LocalMessage(LocalStore.this, null, folder);
message.populateFromGetMessageCursor(cursor);
messages.add(message);
@@ -1317,7 +1288,7 @@ public class LocalStore extends Store implements Serializable {
}
- private String serializeFlags(Flag[] flags) {
+ String serializeFlags(Flag[] flags) {
List extraFlags = new ArrayList();
for (Flag flag : flags) {
@@ -1338,2692 +1309,11 @@ public class LocalStore extends Store implements Serializable {
return Utility.combine(extraFlags.toArray(EMPTY_FLAG_ARRAY), ',').toUpperCase(Locale.US);
}
- public class LocalFolder extends Folder implements Serializable {
- /**
- *
- */
- private static final long serialVersionUID = -1973296520918624767L;
- private String mName = null;
- private long mFolderId = -1;
- private int mVisibleLimit = -1;
- private String prefId = null;
- private FolderClass mDisplayClass = FolderClass.NO_CLASS;
- private FolderClass mSyncClass = FolderClass.INHERITED;
- private FolderClass mPushClass = FolderClass.SECOND_CLASS;
- private FolderClass mNotifyClass = FolderClass.INHERITED;
- private boolean mInTopGroup = false;
- private String mPushState = null;
- private boolean mIntegrate = false;
- // mLastUid is used during syncs. It holds the highest UID within the local folder so we
- // know whether or not an unread message added to the local folder is actually "new" or not.
- private Integer mLastUid = null;
-
- public LocalFolder(String name) {
- super(LocalStore.this.mAccount);
- this.mName = name;
-
- if (LocalStore.this.mAccount.getInboxFolderName().equals(getName())) {
-
- mSyncClass = FolderClass.FIRST_CLASS;
- mPushClass = FolderClass.FIRST_CLASS;
- mInTopGroup = true;
- }
-
-
- }
-
- public LocalFolder(long id) {
- super(LocalStore.this.mAccount);
- this.mFolderId = id;
- }
-
- public long getId() {
- return mFolderId;
- }
-
- @Override
- public void open(final int mode) throws MessagingException {
-
- if (isOpen() && (getMode() == mode || mode == OPEN_MODE_RO)) {
- return;
- } else if (isOpen()) {
- //previously opened in READ_ONLY and now requesting READ_WRITE
- //so close connection and reopen
- close();
- }
-
- try {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- Cursor cursor = null;
- try {
- String baseQuery = "SELECT " + GET_FOLDER_COLS + " FROM folders ";
-
- if (mName != null) {
- cursor = db.rawQuery(baseQuery + "where folders.name = ?", new String[] { mName });
- } else {
- cursor = db.rawQuery(baseQuery + "where folders.id = ?", new String[] { Long.toString(mFolderId) });
- }
-
- if (cursor.moveToFirst() && !cursor.isNull(FOLDER_ID_INDEX)) {
- int folderId = cursor.getInt(FOLDER_ID_INDEX);
- if (folderId > 0) {
- open(cursor);
- }
- } else {
- Log.w(K9.LOG_TAG, "Creating folder " + getName() + " with existing id " + getId());
- create(FolderType.HOLDS_MESSAGES);
- open(mode);
- }
- } catch (MessagingException e) {
- throw new WrappedException(e);
- } finally {
- Utility.closeQuietly(cursor);
- }
- return null;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- private void open(Cursor cursor) throws MessagingException {
- mFolderId = cursor.getInt(FOLDER_ID_INDEX);
- mName = cursor.getString(FOLDER_NAME_INDEX);
- mVisibleLimit = cursor.getInt(FOLDER_VISIBLE_LIMIT_INDEX);
- mPushState = cursor.getString(FOLDER_PUSH_STATE_INDEX);
- super.setStatus(cursor.getString(FOLDER_STATUS_INDEX));
- // Only want to set the local variable stored in the super class. This class
- // does a DB update on setLastChecked
- super.setLastChecked(cursor.getLong(FOLDER_LAST_CHECKED_INDEX));
- super.setLastPush(cursor.getLong(FOLDER_LAST_PUSHED_INDEX));
- mInTopGroup = (cursor.getInt(FOLDER_TOP_GROUP_INDEX)) == 1 ? true : false;
- mIntegrate = (cursor.getInt(FOLDER_INTEGRATE_INDEX) == 1) ? true : false;
- String noClass = FolderClass.NO_CLASS.toString();
- String displayClass = cursor.getString(FOLDER_DISPLAY_CLASS_INDEX);
- mDisplayClass = Folder.FolderClass.valueOf((displayClass == null) ? noClass : displayClass);
- String notifyClass = cursor.getString(FOLDER_NOTIFY_CLASS_INDEX);
- mNotifyClass = Folder.FolderClass.valueOf((notifyClass == null) ? noClass : notifyClass);
- String pushClass = cursor.getString(FOLDER_PUSH_CLASS_INDEX);
- mPushClass = Folder.FolderClass.valueOf((pushClass == null) ? noClass : pushClass);
- String syncClass = cursor.getString(FOLDER_SYNC_CLASS_INDEX);
- mSyncClass = Folder.FolderClass.valueOf((syncClass == null) ? noClass : syncClass);
- }
-
- @Override
- public boolean isOpen() {
- return (mFolderId != -1 && mName != null);
- }
-
- @Override
- public int getMode() {
- return OPEN_MODE_RW;
- }
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public boolean exists() throws MessagingException {
- return database.execute(false, new DbCallback() {
- @Override
- public Boolean doDbWork(final SQLiteDatabase db) throws WrappedException {
- Cursor cursor = null;
- try {
- cursor = db.rawQuery("SELECT id FROM folders "
- + "where folders.name = ?", new String[] { LocalFolder.this
- .getName()
- });
- if (cursor.moveToFirst()) {
- int folderId = cursor.getInt(0);
- return (folderId > 0);
- }
-
- return false;
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
- });
- }
-
- @Override
- public boolean create(FolderType type) throws MessagingException {
- return create(type, mAccount.getDisplayCount());
- }
-
- @Override
- public boolean create(FolderType type, final int visibleLimit) throws MessagingException {
- if (exists()) {
- throw new MessagingException("Folder " + mName + " already exists.");
- }
- List foldersToCreate = new ArrayList(1);
- foldersToCreate.add(this);
- LocalStore.this.createFolders(foldersToCreate, visibleLimit);
-
- return true;
- }
-
- private class PreferencesHolder {
- FolderClass displayClass = mDisplayClass;
- FolderClass syncClass = mSyncClass;
- FolderClass notifyClass = mNotifyClass;
- FolderClass pushClass = mPushClass;
- boolean inTopGroup = mInTopGroup;
- boolean integrate = mIntegrate;
- }
-
- @Override
- public void close() {
- mFolderId = -1;
- }
-
- @Override
- public int getMessageCount() throws MessagingException {
- try {
- return database.execute(false, new DbCallback() {
- @Override
- public Integer doDbWork(final SQLiteDatabase db) throws WrappedException {
- try {
- open(OPEN_MODE_RW);
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- Cursor cursor = null;
- try {
- cursor = db.rawQuery("SELECT COUNT(id) FROM messages WHERE (empty IS NULL OR empty != 1) AND deleted = 0 and folder_id = ?",
- new String[] {
- Long.toString(mFolderId)
- });
- cursor.moveToFirst();
- return cursor.getInt(0); //messagecount
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public int getUnreadMessageCount() throws MessagingException {
- if (mFolderId == -1) {
- open(OPEN_MODE_RW);
- }
-
- try {
- return database.execute(false, new DbCallback() {
- @Override
- public Integer doDbWork(final SQLiteDatabase db) throws WrappedException {
- int unreadMessageCount = 0;
- Cursor cursor = db.query("messages", new String[] { "COUNT(id)" },
- "folder_id = ? AND (empty IS NULL OR empty != 1) AND deleted = 0 AND read=0",
- new String[] { Long.toString(mFolderId) }, null, null, null);
-
- try {
- if (cursor.moveToFirst()) {
- unreadMessageCount = cursor.getInt(0);
- }
- } finally {
- cursor.close();
- }
-
- return unreadMessageCount;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public int getFlaggedMessageCount() throws MessagingException {
- if (mFolderId == -1) {
- open(OPEN_MODE_RW);
- }
-
- try {
- return database.execute(false, new DbCallback() {
- @Override
- public Integer doDbWork(final SQLiteDatabase db) throws WrappedException {
- int flaggedMessageCount = 0;
- Cursor cursor = db.query("messages", new String[] { "COUNT(id)" },
- "folder_id = ? AND (empty IS NULL OR empty != 1) AND deleted = 0 AND flagged = 1",
- new String[] { Long.toString(mFolderId) }, null, null, null);
-
- try {
- if (cursor.moveToFirst()) {
- flaggedMessageCount = cursor.getInt(0);
- }
- } finally {
- cursor.close();
- }
-
- return flaggedMessageCount;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public void setLastChecked(final long lastChecked) throws MessagingException {
- try {
- open(OPEN_MODE_RW);
- LocalFolder.super.setLastChecked(lastChecked);
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- updateFolderColumn("last_updated", lastChecked);
- }
-
- @Override
- public void setLastPush(final long lastChecked) throws MessagingException {
- try {
- open(OPEN_MODE_RW);
- LocalFolder.super.setLastPush(lastChecked);
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- updateFolderColumn("last_pushed", lastChecked);
- }
-
- public int getVisibleLimit() throws MessagingException {
- open(OPEN_MODE_RW);
- return mVisibleLimit;
- }
-
- public void purgeToVisibleLimit(MessageRemovalListener listener) throws MessagingException {
- //don't purge messages while a Search is active since it might throw away search results
- if (!Search.isActive()) {
- if (mVisibleLimit == 0) {
- return ;
- }
- open(OPEN_MODE_RW);
- Message[] messages = getMessages(null, false);
- for (int i = mVisibleLimit; i < messages.length; i++) {
- if (listener != null) {
- listener.messageRemoved(messages[i]);
- }
- messages[i].destroy();
- }
- }
- }
-
-
- public void setVisibleLimit(final int visibleLimit) throws MessagingException {
- mVisibleLimit = visibleLimit;
- updateFolderColumn("visible_limit", mVisibleLimit);
- }
-
- @Override
- public void setStatus(final String status) throws MessagingException {
- updateFolderColumn("status", status);
- }
- public void setPushState(final String pushState) throws MessagingException {
- mPushState = pushState;
- updateFolderColumn("push_state", pushState);
- }
-
- private void updateFolderColumn(final String column, final Object value) throws MessagingException {
- try {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- try {
- open(OPEN_MODE_RW);
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- db.execSQL("UPDATE folders SET " + column + " = ? WHERE id = ?", new Object[] { value, mFolderId });
- return null;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- public String getPushState() {
- return mPushState;
- }
-
- @Override
- public FolderClass getDisplayClass() {
- return mDisplayClass;
- }
-
- @Override
- public FolderClass getSyncClass() {
- return (FolderClass.INHERITED == mSyncClass) ? getDisplayClass() : mSyncClass;
- }
-
- public FolderClass getRawSyncClass() {
- return mSyncClass;
- }
-
- public FolderClass getNotifyClass() {
- return (FolderClass.INHERITED == mNotifyClass) ? getPushClass() : mNotifyClass;
- }
-
- public FolderClass getRawNotifyClass() {
- return mNotifyClass;
- }
-
- @Override
- public FolderClass getPushClass() {
- return (FolderClass.INHERITED == mPushClass) ? getSyncClass() : mPushClass;
- }
-
- public FolderClass getRawPushClass() {
- return mPushClass;
- }
-
- public void setDisplayClass(FolderClass displayClass) throws MessagingException {
- mDisplayClass = displayClass;
- updateFolderColumn("display_class", mDisplayClass.name());
-
- }
-
- public void setSyncClass(FolderClass syncClass) throws MessagingException {
- mSyncClass = syncClass;
- updateFolderColumn("poll_class", mSyncClass.name());
- }
-
- public void setPushClass(FolderClass pushClass) throws MessagingException {
- mPushClass = pushClass;
- updateFolderColumn("push_class", mPushClass.name());
- }
-
- public void setNotifyClass(FolderClass notifyClass) throws MessagingException {
- mNotifyClass = notifyClass;
- updateFolderColumn("notify_class", mNotifyClass.name());
- }
-
- public boolean isIntegrate() {
- return mIntegrate;
- }
-
- public void setIntegrate(boolean integrate) throws MessagingException {
- mIntegrate = integrate;
- updateFolderColumn("integrate", mIntegrate ? 1 : 0);
- }
-
- private String getPrefId(String name) {
- if (prefId == null) {
- prefId = uUid + "." + name;
- }
-
- return prefId;
- }
-
- private String getPrefId() throws MessagingException {
- open(OPEN_MODE_RW);
- return getPrefId(mName);
-
- }
-
- public void delete() throws MessagingException {
- String id = getPrefId();
-
- SharedPreferences.Editor editor = LocalStore.this.getPreferences().edit();
-
- editor.remove(id + ".displayMode");
- editor.remove(id + ".syncMode");
- editor.remove(id + ".pushMode");
- editor.remove(id + ".inTopGroup");
- editor.remove(id + ".integrate");
-
- editor.commit();
- }
-
- public void save() throws MessagingException {
- SharedPreferences.Editor editor = LocalStore.this.getPreferences().edit();
- save(editor);
- editor.commit();
- }
-
- public void save(SharedPreferences.Editor editor) throws MessagingException {
- String id = getPrefId();
-
- // there can be a lot of folders. For the defaults, let's not save prefs, saving space, except for INBOX
- if (mDisplayClass == FolderClass.NO_CLASS && !mAccount.getInboxFolderName().equals(getName())) {
- editor.remove(id + ".displayMode");
- } else {
- editor.putString(id + ".displayMode", mDisplayClass.name());
- }
-
- if (mSyncClass == FolderClass.INHERITED && !mAccount.getInboxFolderName().equals(getName())) {
- editor.remove(id + ".syncMode");
- } else {
- editor.putString(id + ".syncMode", mSyncClass.name());
- }
-
- if (mNotifyClass == FolderClass.INHERITED && !mAccount.getInboxFolderName().equals(getName())) {
- editor.remove(id + ".notifyMode");
- } else {
- editor.putString(id + ".notifyMode", mNotifyClass.name());
- }
-
- if (mPushClass == FolderClass.SECOND_CLASS && !mAccount.getInboxFolderName().equals(getName())) {
- editor.remove(id + ".pushMode");
- } else {
- editor.putString(id + ".pushMode", mPushClass.name());
- }
- editor.putBoolean(id + ".inTopGroup", mInTopGroup);
-
- editor.putBoolean(id + ".integrate", mIntegrate);
-
- }
-
- public void refresh(String name, PreferencesHolder prefHolder) {
- String id = getPrefId(name);
-
- SharedPreferences preferences = LocalStore.this.getPreferences();
-
- try {
- prefHolder.displayClass = FolderClass.valueOf(preferences.getString(id + ".displayMode",
- prefHolder.displayClass.name()));
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Unable to load displayMode for " + getName(), e);
- }
- if (prefHolder.displayClass == FolderClass.NONE) {
- prefHolder.displayClass = FolderClass.NO_CLASS;
- }
-
- try {
- prefHolder.syncClass = FolderClass.valueOf(preferences.getString(id + ".syncMode",
- prefHolder.syncClass.name()));
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Unable to load syncMode for " + getName(), e);
-
- }
- if (prefHolder.syncClass == FolderClass.NONE) {
- prefHolder.syncClass = FolderClass.INHERITED;
- }
-
- try {
- prefHolder.notifyClass = FolderClass.valueOf(preferences.getString(id + ".notifyMode",
- prefHolder.notifyClass.name()));
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Unable to load notifyMode for " + getName(), e);
- }
- if (prefHolder.notifyClass == FolderClass.NONE) {
- prefHolder.notifyClass = FolderClass.INHERITED;
- }
-
- try {
- prefHolder.pushClass = FolderClass.valueOf(preferences.getString(id + ".pushMode",
- prefHolder.pushClass.name()));
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Unable to load pushMode for " + getName(), e);
- }
- if (prefHolder.pushClass == FolderClass.NONE) {
- prefHolder.pushClass = FolderClass.INHERITED;
- }
- prefHolder.inTopGroup = preferences.getBoolean(id + ".inTopGroup", prefHolder.inTopGroup);
- prefHolder.integrate = preferences.getBoolean(id + ".integrate", prefHolder.integrate);
-
- }
-
- @Override
- public void fetch(final Message[] messages, final FetchProfile fp, final MessageRetrievalListener listener)
- throws MessagingException {
- try {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- try {
- open(OPEN_MODE_RW);
- if (fp.contains(FetchProfile.Item.BODY)) {
- for (Message message : messages) {
- LocalMessage localMessage = (LocalMessage)message;
- Cursor cursor = null;
- MimeMultipart mp = new MimeMultipart();
- mp.setSubType("mixed");
- try {
- cursor = db.rawQuery("SELECT html_content, text_content, mime_type FROM messages "
- + "WHERE id = ?",
- new String[] { Long.toString(localMessage.mId) });
- cursor.moveToNext();
- String htmlContent = cursor.getString(0);
- String textContent = cursor.getString(1);
- String mimeType = cursor.getString(2);
- if (mimeType != null && mimeType.toLowerCase(Locale.US).startsWith("multipart/")) {
- // If this is a multipart message, preserve both text
- // and html parts, as well as the subtype.
- mp.setSubType(mimeType.toLowerCase(Locale.US).replaceFirst("^multipart/", ""));
- if (textContent != null) {
- LocalTextBody body = new LocalTextBody(textContent, htmlContent);
- MimeBodyPart bp = new MimeBodyPart(body, "text/plain");
- mp.addBodyPart(bp);
- }
-
- if (mAccount.getMessageFormat() != MessageFormat.TEXT) {
- if (htmlContent != null) {
- TextBody body = new TextBody(htmlContent);
- MimeBodyPart bp = new MimeBodyPart(body, "text/html");
- mp.addBodyPart(bp);
- }
-
- // If we have both text and html content and our MIME type
- // isn't multipart/alternative, then corral them into a new
- // multipart/alternative part and put that into the parent.
- // If it turns out that this is the only part in the parent
- // MimeMultipart, it'll get fixed below before we attach to
- // the message.
- if (textContent != null && htmlContent != null && !mimeType.equalsIgnoreCase("multipart/alternative")) {
- MimeMultipart alternativeParts = mp;
- alternativeParts.setSubType("alternative");
- mp = new MimeMultipart();
- mp.addBodyPart(new MimeBodyPart(alternativeParts));
- }
- }
- } else if (mimeType != null && mimeType.equalsIgnoreCase("text/plain")) {
- // If it's text, add only the plain part. The MIME
- // container will drop away below.
- if (textContent != null) {
- LocalTextBody body = new LocalTextBody(textContent, htmlContent);
- MimeBodyPart bp = new MimeBodyPart(body, "text/plain");
- mp.addBodyPart(bp);
- }
- } else if (mimeType != null && mimeType.equalsIgnoreCase("text/html")) {
- // If it's html, add only the html part. The MIME
- // container will drop away below.
- if (htmlContent != null) {
- TextBody body = new TextBody(htmlContent);
- MimeBodyPart bp = new MimeBodyPart(body, "text/html");
- mp.addBodyPart(bp);
- }
- } else {
- // MIME type not set. Grab whatever part we can get,
- // with Text taking precedence. This preserves pre-HTML
- // composition behaviour.
- if (textContent != null) {
- LocalTextBody body = new LocalTextBody(textContent, htmlContent);
- MimeBodyPart bp = new MimeBodyPart(body, "text/plain");
- mp.addBodyPart(bp);
- } else if (htmlContent != null) {
- TextBody body = new TextBody(htmlContent);
- MimeBodyPart bp = new MimeBodyPart(body, "text/html");
- mp.addBodyPart(bp);
- }
- }
-
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Exception fetching message:", e);
- } finally {
- Utility.closeQuietly(cursor);
- }
-
- try {
- cursor = db.query(
- "attachments",
- new String[] {
- "id",
- "size",
- "name",
- "mime_type",
- "store_data",
- "content_uri",
- "content_id",
- "content_disposition"
- },
- "message_id = ?",
- new String[] { Long.toString(localMessage.mId) },
- null,
- null,
- null);
-
- while (cursor.moveToNext()) {
- long id = cursor.getLong(0);
- int size = cursor.getInt(1);
- String name = cursor.getString(2);
- String type = cursor.getString(3);
- String storeData = cursor.getString(4);
- String contentUri = cursor.getString(5);
- String contentId = cursor.getString(6);
- String contentDisposition = cursor.getString(7);
- String encoding = MimeUtility.getEncodingforType(type);
- Body body = null;
-
- if (contentDisposition == null) {
- contentDisposition = "attachment";
- }
-
- if (contentUri != null) {
- if (MimeUtil.isMessage(type)) {
- body = new LocalAttachmentMessageBody(
- Uri.parse(contentUri),
- mApplication);
- } else {
- body = new LocalAttachmentBody(
- Uri.parse(contentUri),
- mApplication);
- }
- }
-
- MimeBodyPart bp = new LocalAttachmentBodyPart(body, id);
- bp.setEncoding(encoding);
- if (name != null) {
- bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
- String.format("%s;\r\n name=\"%s\"",
- type,
- name));
- bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
- String.format(Locale.US, "%s;\r\n filename=\"%s\";\r\n size=%d",
- contentDisposition,
- name, // TODO: Should use encoded word defined in RFC 2231.
- size));
- } else {
- bp.setHeader(MimeHeader.HEADER_CONTENT_TYPE, type);
- bp.setHeader(MimeHeader.HEADER_CONTENT_DISPOSITION,
- String.format(Locale.US, "%s;\r\n size=%d",
- contentDisposition,
- size));
- }
-
- bp.setHeader(MimeHeader.HEADER_CONTENT_ID, contentId);
- /*
- * HEADER_ANDROID_ATTACHMENT_STORE_DATA is a custom header we add to that
- * we can later pull the attachment from the remote store if necessary.
- */
- bp.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, storeData);
-
- mp.addBodyPart(bp);
- }
- } finally {
- Utility.closeQuietly(cursor);
- }
-
- if (mp.getCount() == 0) {
- // If we have no body, remove the container and create a
- // dummy plain text body. This check helps prevents us from
- // triggering T_MIME_NO_TEXT and T_TVD_MIME_NO_HEADERS
- // SpamAssassin rules.
- localMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, "text/plain");
- localMessage.setBody(new TextBody(""));
- } else if (mp.getCount() == 1 && (mp.getBodyPart(0) instanceof LocalAttachmentBodyPart) == false)
-
- {
- // If we have only one part, drop the MimeMultipart container.
- BodyPart part = mp.getBodyPart(0);
- localMessage.setHeader(MimeHeader.HEADER_CONTENT_TYPE, part.getContentType());
- localMessage.setBody(part.getBody());
- } else {
- // Otherwise, attach the MimeMultipart to the message.
- localMessage.setBody(mp);
- }
- }
- }
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- return null;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
- throws MessagingException {
- open(OPEN_MODE_RW);
- throw new MessagingException(
- "LocalStore.getMessages(int, int, MessageRetrievalListener) not yet implemented");
- }
-
- /**
- * Populate the header fields of the given list of messages by reading
- * the saved header data from the database.
- *
- * @param messages
- * The messages whose headers should be loaded.
- * @throws UnavailableStorageException
- */
- private void populateHeaders(final List messages) throws UnavailableStorageException {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- Cursor cursor = null;
- if (messages.isEmpty()) {
- return null;
- }
- try {
- Map popMessages = new HashMap();
- List ids = new ArrayList();
- StringBuilder questions = new StringBuilder();
-
- for (int i = 0; i < messages.size(); i++) {
- if (i != 0) {
- questions.append(", ");
- }
- questions.append("?");
- LocalMessage message = messages.get(i);
- Long id = message.getId();
- ids.add(Long.toString(id));
- popMessages.put(id, message);
-
- }
-
- cursor = db.rawQuery(
- "SELECT message_id, name, value FROM headers " + "WHERE message_id in ( " + questions + ") ORDER BY id ASC",
- ids.toArray(EMPTY_STRING_ARRAY));
-
-
- while (cursor.moveToNext()) {
- Long id = cursor.getLong(0);
- String name = cursor.getString(1);
- String value = cursor.getString(2);
- //Log.i(K9.LOG_TAG, "Retrieved header name= " + name + ", value = " + value + " for message " + id);
- popMessages.get(id).addHeader(name, value);
- }
- } finally {
- Utility.closeQuietly(cursor);
- }
- return null;
- }
- });
- }
-
- public String getMessageUidById(final long id) throws MessagingException {
- try {
- return database.execute(false, new DbCallback() {
- @Override
- public String doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- open(OPEN_MODE_RW);
- Cursor cursor = null;
-
- try {
- cursor = db.rawQuery(
- "SELECT uid FROM messages " +
- "WHERE id = ? AND folder_id = ?",
- new String[] {
- Long.toString(id), Long.toString(mFolderId)
- });
- if (!cursor.moveToNext()) {
- return null;
- }
- return cursor.getString(0);
- } finally {
- Utility.closeQuietly(cursor);
- }
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public LocalMessage getMessage(final String uid) throws MessagingException {
- try {
- return database.execute(false, new DbCallback() {
- @Override
- public LocalMessage doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- open(OPEN_MODE_RW);
- LocalMessage message = new LocalMessage(uid, LocalFolder.this);
- Cursor cursor = null;
-
- try {
- cursor = db.rawQuery(
- "SELECT " +
- GET_MESSAGES_COLS +
- "FROM messages " +
- "LEFT JOIN threads ON (threads.message_id = messages.id) " +
- "WHERE uid = ? AND folder_id = ?",
- new String[] {
- message.getUid(), Long.toString(mFolderId)
- });
- if (!cursor.moveToNext()) {
- return null;
- }
- message.populateFromGetMessageCursor(cursor);
- } finally {
- Utility.closeQuietly(cursor);
- }
- return message;
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
- return getMessages(listener, true);
- }
-
- @Override
- public Message[] getMessages(final MessageRetrievalListener listener, final boolean includeDeleted) throws MessagingException {
- try {
- return database.execute(false, new DbCallback() {
- @Override
- public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- open(OPEN_MODE_RW);
- return LocalStore.this.getMessages(
- listener,
- LocalFolder.this,
- "SELECT " + GET_MESSAGES_COLS +
- "FROM messages " +
- "LEFT JOIN threads ON (threads.message_id = messages.id) " +
- "WHERE (empty IS NULL OR empty != 1) AND " +
- (includeDeleted ? "" : "deleted = 0 AND ") +
- "folder_id = ? ORDER BY date DESC",
- new String[] { Long.toString(mFolderId) }
- );
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
- throws MessagingException {
- open(OPEN_MODE_RW);
- if (uids == null) {
- return getMessages(listener);
- }
- ArrayList messages = new ArrayList();
- for (String uid : uids) {
- Message message = getMessage(uid);
- if (message != null) {
- messages.add(message);
- }
- }
- return messages.toArray(EMPTY_MESSAGE_ARRAY);
- }
-
- @Override
- public Map copyMessages(Message[] msgs, Folder folder) throws MessagingException {
- if (!(folder instanceof LocalFolder)) {
- throw new MessagingException("copyMessages called with incorrect Folder");
- }
- return ((LocalFolder) folder).appendMessages(msgs, true);
- }
-
- @Override
- public Map moveMessages(final Message[] msgs, final Folder destFolder) throws MessagingException {
- if (!(destFolder instanceof LocalFolder)) {
- throw new MessagingException("moveMessages called with non-LocalFolder");
- }
-
- final LocalFolder lDestFolder = (LocalFolder)destFolder;
-
- final Map uidMap = new HashMap();
-
- try {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- lDestFolder.open(OPEN_MODE_RW);
- for (Message message : msgs) {
- LocalMessage lMessage = (LocalMessage)message;
-
- String oldUID = message.getUid();
-
- if (K9.DEBUG) {
- Log.d(K9.LOG_TAG, "Updating folder_id to " + lDestFolder.getId() + " for message with UID "
- + message.getUid() + ", id " + lMessage.getId() + " currently in folder " + getName());
- }
-
- String newUid = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString();
- message.setUid(newUid);
-
- uidMap.put(oldUID, newUid);
-
- // Message threading in the target folder
- ThreadInfo threadInfo = lDestFolder.doMessageThreading(db, message);
-
- /*
- * "Move" the message into the new folder
- */
- long msgId = lMessage.getId();
- String[] idArg = new String[] { Long.toString(msgId) };
-
- ContentValues cv = new ContentValues();
- cv.put("folder_id", lDestFolder.getId());
- cv.put("uid", newUid);
-
- db.update("messages", cv, "id = ?", idArg);
-
- // Create/update entry in 'threads' table for the message in the
- // target folder
- cv.clear();
- cv.put("message_id", msgId);
- if (threadInfo.threadId == -1) {
- if (threadInfo.rootId != -1) {
- cv.put("root", threadInfo.rootId);
- }
-
- if (threadInfo.parentId != -1) {
- cv.put("parent", threadInfo.parentId);
- }
-
- db.insert("threads", null, cv);
- } else {
- db.update("threads", cv, "id = ?",
- new String[] { Long.toString(threadInfo.threadId) });
- }
-
- /*
- * Add a placeholder message so we won't download the original
- * message again if we synchronize before the remote move is
- * complete.
- */
-
- // We need to open this folder to get the folder id
- open(OPEN_MODE_RW);
-
- cv.clear();
- cv.put("uid", oldUID);
- cv.putNull("flags");
- cv.put("read", 1);
- cv.put("deleted", 1);
- cv.put("folder_id", mFolderId);
- cv.put("empty", 0);
-
- String messageId = message.getMessageId();
- if (messageId != null) {
- cv.put("message_id", messageId);
- }
-
- final long newId;
- if (threadInfo.msgId != -1) {
- // There already existed an empty message in the target folder.
- // Let's use it as placeholder.
-
- newId = threadInfo.msgId;
-
- db.update("messages", cv, "id = ?",
- new String[] { Long.toString(newId) });
- } else {
- newId = db.insert("messages", null, cv);
- }
-
- /*
- * Update old entry in 'threads' table to point to the newly
- * created placeholder.
- */
-
- cv.clear();
- cv.put("message_id", newId);
- db.update("threads", cv, "id = ?",
- new String[] { Long.toString(lMessage.getThreadId()) });
- }
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- return null;
- }
- });
-
- notifyChange();
-
- return uidMap;
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
-
- }
-
- /**
- * Convenience transaction wrapper for storing a message and set it as fully downloaded. Implemented mainly to speed up DB transaction commit.
- *
- * @param message Message to store. Never null.
- * @param runnable What to do before setting {@link Flag#X_DOWNLOADED_FULL}. Never null.
- * @return The local version of the message. Never null.
- * @throws MessagingException
- */
- public Message storeSmallMessage(final Message message, final Runnable runnable) throws MessagingException {
- return database.execute(true, new DbCallback() {
- @Override
- public Message doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- appendMessages(new Message[] { message });
- final String uid = message.getUid();
- final Message result = getMessage(uid);
- runnable.run();
- // Set a flag indicating this message has now be fully downloaded
- result.setFlag(Flag.X_DOWNLOADED_FULL, true);
- return result;
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
- });
- }
-
- /**
- * The method differs slightly from the contract; If an incoming message already has a uid
- * assigned and it matches the uid of an existing message then this message will replace the
- * old message. It is implemented as a delete/insert. This functionality is used in saving
- * of drafts and re-synchronization of updated server messages.
- *
- * NOTE that although this method is located in the LocalStore class, it is not guaranteed
- * that the messages supplied as parameters are actually {@link LocalMessage} instances (in
- * fact, in most cases, they are not). Therefore, if you want to make local changes only to a
- * message, retrieve the appropriate local message instance first (if it already exists).
- */
- @Override
- public Map appendMessages(Message[] messages) throws MessagingException {
- return appendMessages(messages, false);
- }
-
- public void destroyMessages(final Message[] messages) {
- try {
- database.execute(true, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- for (Message message : messages) {
- try {
- message.destroy();
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
- return null;
- }
- });
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
-
- private ThreadInfo getThreadInfo(SQLiteDatabase db, String messageId, boolean onlyEmpty) {
- String sql = "SELECT t.id, t.message_id, t.root, t.parent " +
- "FROM messages m " +
- "LEFT JOIN threads t ON (t.message_id = m.id) " +
- "WHERE m.folder_id = ? AND m.message_id = ? " +
- ((onlyEmpty) ? "AND m.empty = 1 " : "") +
- "ORDER BY m.id LIMIT 1";
- String[] selectionArgs = { Long.toString(mFolderId), messageId };
- Cursor cursor = db.rawQuery(sql, selectionArgs);
-
- if (cursor != null) {
- try {
- if (cursor.getCount() > 0) {
- cursor.moveToFirst();
- long threadId = cursor.getLong(0);
- long msgId = cursor.getLong(1);
- long rootId = (cursor.isNull(2)) ? -1 : cursor.getLong(2);
- long parentId = (cursor.isNull(3)) ? -1 : cursor.getLong(3);
-
- return new ThreadInfo(threadId, msgId, messageId, rootId, parentId);
- }
- } finally {
- cursor.close();
- }
- }
-
- return null;
- }
-
- /**
- * The method differs slightly from the contract; If an incoming message already has a uid
- * assigned and it matches the uid of an existing message then this message will replace
- * the old message. This functionality is used in saving of drafts and re-synchronization
- * of updated server messages.
- *
- * NOTE that although this method is located in the LocalStore class, it is not guaranteed
- * that the messages supplied as parameters are actually {@link LocalMessage} instances (in
- * fact, in most cases, they are not). Therefore, if you want to make local changes only to a
- * message, retrieve the appropriate local message instance first (if it already exists).
- * @param messages
- * @param copy
- * @return Map uidMap of srcUids -> destUids
- */
- private Map appendMessages(final Message[] messages, final boolean copy) throws MessagingException {
- open(OPEN_MODE_RW);
- try {
- final Map uidMap = new HashMap();
- database.execute(true, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- for (Message message : messages) {
- if (!(message instanceof MimeMessage)) {
- throw new Error("LocalStore can only store Messages that extend MimeMessage");
- }
-
- long oldMessageId = -1;
- String uid = message.getUid();
- if (uid == null || copy) {
- /*
- * Create a new message in the database
- */
- String randomLocalUid = K9.LOCAL_UID_PREFIX +
- UUID.randomUUID().toString();
-
- if (copy) {
- // Save mapping: source UID -> target UID
- uidMap.put(uid, randomLocalUid);
- } else {
- // Modify the Message instance to reference the new UID
- message.setUid(randomLocalUid);
- }
-
- // The message will be saved with the newly generated UID
- uid = randomLocalUid;
- } else {
- /*
- * Replace an existing message in the database
- */
- LocalMessage oldMessage = getMessage(uid);
-
- if (oldMessage != null) {
- oldMessageId = oldMessage.getId();
- }
-
- deleteAttachments(message.getUid());
- }
-
- long rootId = -1;
- long parentId = -1;
-
- if (oldMessageId == -1) {
- // This is a new message. Do the message threading.
- ThreadInfo threadInfo = doMessageThreading(db, message);
- oldMessageId = threadInfo.msgId;
- rootId = threadInfo.rootId;
- parentId = threadInfo.parentId;
- }
-
- boolean isDraft = (message.getHeader(K9.IDENTITY_HEADER) != null);
-
- List attachments;
- String text;
- String html;
- if (isDraft) {
- // Don't modify the text/plain or text/html part of our own
- // draft messages because this will cause the values stored in
- // the identity header to be wrong.
- ViewableContainer container =
- MimeUtility.extractPartsFromDraft(message);
-
- text = container.text;
- html = container.html;
- attachments = container.attachments;
- } else {
- ViewableContainer container =
- MimeUtility.extractTextAndAttachments(mApplication, message);
-
- attachments = container.attachments;
- text = container.text;
- html = HtmlConverter.convertEmoji2Img(container.html);
- }
-
- String preview = Message.calculateContentPreview(text);
-
- try {
- ContentValues cv = new ContentValues();
- cv.put("uid", uid);
- cv.put("subject", message.getSubject());
- cv.put("sender_list", Address.pack(message.getFrom()));
- cv.put("date", message.getSentDate() == null
- ? System.currentTimeMillis() : message.getSentDate().getTime());
- cv.put("flags", serializeFlags(message.getFlags()));
- cv.put("deleted", message.isSet(Flag.DELETED) ? 1 : 0);
- cv.put("read", message.isSet(Flag.SEEN) ? 1 : 0);
- cv.put("flagged", message.isSet(Flag.FLAGGED) ? 1 : 0);
- cv.put("answered", message.isSet(Flag.ANSWERED) ? 1 : 0);
- cv.put("forwarded", message.isSet(Flag.FORWARDED) ? 1 : 0);
- cv.put("folder_id", mFolderId);
- cv.put("to_list", Address.pack(message.getRecipients(RecipientType.TO)));
- cv.put("cc_list", Address.pack(message.getRecipients(RecipientType.CC)));
- cv.put("bcc_list", Address.pack(message.getRecipients(RecipientType.BCC)));
- cv.put("html_content", html.length() > 0 ? html : null);
- cv.put("text_content", text.length() > 0 ? text : null);
- cv.put("preview", preview.length() > 0 ? preview : null);
- cv.put("reply_to_list", Address.pack(message.getReplyTo()));
- cv.put("attachment_count", attachments.size());
- cv.put("internal_date", message.getInternalDate() == null
- ? System.currentTimeMillis() : message.getInternalDate().getTime());
- cv.put("mime_type", message.getMimeType());
- cv.put("empty", 0);
-
- String messageId = message.getMessageId();
- if (messageId != null) {
- cv.put("message_id", messageId);
- }
-
- long msgId;
-
- if (oldMessageId == -1) {
- msgId = db.insert("messages", "uid", cv);
-
- // Create entry in 'threads' table
- cv.clear();
- cv.put("message_id", msgId);
-
- if (rootId != -1) {
- cv.put("root", rootId);
- }
- if (parentId != -1) {
- cv.put("parent", parentId);
- }
-
- db.insert("threads", null, cv);
- } else {
- db.update("messages", cv, "id = ?", new String[] { Long.toString(oldMessageId) });
- msgId = oldMessageId;
- }
-
- for (Part attachment : attachments) {
- saveAttachment(msgId, attachment, copy);
- }
- saveHeaders(msgId, (MimeMessage)message);
- } catch (Exception e) {
- throw new MessagingException("Error appending message", e);
- }
- }
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- return null;
- }
- });
-
- notifyChange();
-
- return uidMap;
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- /**
- * Update the given message in the LocalStore without first deleting the existing
- * message (contrast with appendMessages). This method is used to store changes
- * to the given message while updating attachments and not removing existing
- * attachment data.
- * TODO In the future this method should be combined with appendMessages since the Message
- * contains enough data to decide what to do.
- * @param message
- * @throws MessagingException
- */
- public void updateMessage(final LocalMessage message) throws MessagingException {
- open(OPEN_MODE_RW);
- try {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- message.buildMimeRepresentation();
-
- ViewableContainer container =
- MimeUtility.extractTextAndAttachments(mApplication, message);
-
- List attachments = container.attachments;
- String text = container.text;
- String html = HtmlConverter.convertEmoji2Img(container.html);
-
- String preview = Message.calculateContentPreview(text);
-
- try {
- db.execSQL("UPDATE messages SET "
- + "uid = ?, subject = ?, sender_list = ?, date = ?, flags = ?, "
- + "folder_id = ?, to_list = ?, cc_list = ?, bcc_list = ?, "
- + "html_content = ?, text_content = ?, preview = ?, reply_to_list = ?, "
- + "attachment_count = ?, read = ?, flagged = ?, answered = ?, forwarded = ? "
- + "WHERE id = ?",
- new Object[] {
- message.getUid(),
- message.getSubject(),
- Address.pack(message.getFrom()),
- message.getSentDate() == null ? System
- .currentTimeMillis() : message.getSentDate()
- .getTime(),
- serializeFlags(message.getFlags()),
- mFolderId,
- Address.pack(message
- .getRecipients(RecipientType.TO)),
- Address.pack(message
- .getRecipients(RecipientType.CC)),
- Address.pack(message
- .getRecipients(RecipientType.BCC)),
- html.length() > 0 ? html : null,
- text.length() > 0 ? text : null,
- preview.length() > 0 ? preview : null,
- Address.pack(message.getReplyTo()),
- attachments.size(),
- message.isSet(Flag.SEEN) ? 1 : 0,
- message.isSet(Flag.FLAGGED) ? 1 : 0,
- message.isSet(Flag.ANSWERED) ? 1 : 0,
- message.isSet(Flag.FORWARDED) ? 1 : 0,
- message.mId
- });
-
- for (int i = 0, count = attachments.size(); i < count; i++) {
- Part attachment = attachments.get(i);
- saveAttachment(message.mId, attachment, false);
- }
- saveHeaders(message.getId(), message);
- } catch (Exception e) {
- throw new MessagingException("Error appending message", e);
- }
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- return null;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
-
- notifyChange();
- }
-
- /**
- * Save the headers of the given message. Note that the message is not
- * necessarily a {@link LocalMessage} instance.
- * @param id
- * @param message
- * @throws com.fsck.k9.mail.MessagingException
- */
- private void saveHeaders(final long id, final MimeMessage message) throws MessagingException {
- database.execute(true, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
-
- deleteHeaders(id);
- for (String name : message.getHeaderNames()) {
- String[] values = message.getHeader(name);
- for (String value : values) {
- ContentValues cv = new ContentValues();
- cv.put("message_id", id);
- cv.put("name", name);
- cv.put("value", value);
- db.insert("headers", "name", cv);
- }
- }
-
- // Remember that all headers for this message have been saved, so it is
- // not necessary to download them again in case the user wants to see all headers.
- List appendedFlags = new ArrayList();
- appendedFlags.addAll(Arrays.asList(message.getFlags()));
- appendedFlags.add(Flag.X_GOT_ALL_HEADERS);
-
- db.execSQL("UPDATE messages " + "SET flags = ? " + " WHERE id = ?",
- new Object[]
- { serializeFlags(appendedFlags.toArray(EMPTY_FLAG_ARRAY)), id });
-
- return null;
- }
- });
- }
-
- private void deleteHeaders(final long id) throws UnavailableStorageException {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- db.execSQL("DELETE FROM headers WHERE message_id = ?", new Object[]
- { id });
- return null;
- }
- });
- }
-
- /**
- * @param messageId
- * @param attachment
- * @param saveAsNew
- * @throws IOException
- * @throws MessagingException
- */
- private void saveAttachment(final long messageId, final Part attachment, final boolean saveAsNew)
- throws IOException, MessagingException {
- try {
- database.execute(true, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- long attachmentId = -1;
- Uri contentUri = null;
- int size = -1;
- File tempAttachmentFile = null;
-
- if ((!saveAsNew) && (attachment instanceof LocalAttachmentBodyPart)) {
- attachmentId = ((LocalAttachmentBodyPart) attachment).getAttachmentId();
- }
-
- final File attachmentDirectory = StorageManager.getInstance(mApplication).getAttachmentDirectory(uUid, database.getStorageProviderId());
- if (attachment.getBody() != null) {
- Body body = attachment.getBody();
- if (body instanceof LocalAttachmentBody) {
- contentUri = ((LocalAttachmentBody) body).getContentUri();
- } else if (body instanceof Message) {
- // It's a message, so use Message.writeTo() to output the
- // message including all children.
- Message message = (Message) body;
- tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
- FileOutputStream out = new FileOutputStream(tempAttachmentFile);
- try {
- message.writeTo(out);
- } finally {
- out.close();
- }
- size = (int) (tempAttachmentFile.length() & 0x7FFFFFFFL);
- } else {
- /*
- * If the attachment has a body we're expected to save it into the local store
- * so we copy the data into a cached attachment file.
- */
- InputStream in = attachment.getBody().getInputStream();
- try {
- tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
- FileOutputStream out = new FileOutputStream(tempAttachmentFile);
- try {
- size = IOUtils.copy(in, out);
- } finally {
- out.close();
- }
- } finally {
- try { in.close(); } catch (Throwable ignore) {}
- }
- }
- }
-
- if (size == -1) {
- /*
- * If the attachment is not yet downloaded see if we can pull a size
- * off the Content-Disposition.
- */
- String disposition = attachment.getDisposition();
- if (disposition != null) {
- String sizeParam = MimeUtility.getHeaderParameter(disposition, "size");
- if (sizeParam != null) {
- try {
- size = Integer.parseInt(sizeParam);
- } catch (NumberFormatException e) { /* Ignore */ }
- }
- }
- }
- if (size == -1) {
- size = 0;
- }
-
- String storeData =
- Utility.combine(attachment.getHeader(
- MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA), ',');
-
- String name = MimeUtility.getHeaderParameter(attachment.getContentType(), "name");
- String contentId = MimeUtility.getHeaderParameter(attachment.getContentId(), null);
-
- String contentDisposition = MimeUtility.unfoldAndDecode(attachment.getDisposition());
- String dispositionType = contentDisposition;
-
- if (dispositionType != null) {
- int pos = dispositionType.indexOf(';');
- if (pos != -1) {
- // extract the disposition-type, "attachment", "inline" or extension-token (see the RFC 2183)
- dispositionType = dispositionType.substring(0, pos);
- }
- }
-
- if (name == null && contentDisposition != null) {
- name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
- }
- if (attachmentId == -1) {
- ContentValues cv = new ContentValues();
- cv.put("message_id", messageId);
- cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
- cv.put("store_data", storeData);
- cv.put("size", size);
- cv.put("name", name);
- cv.put("mime_type", attachment.getMimeType());
- cv.put("content_id", contentId);
- cv.put("content_disposition", dispositionType);
-
- attachmentId = db.insert("attachments", "message_id", cv);
- } else {
- ContentValues cv = new ContentValues();
- cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
- cv.put("size", size);
- db.update("attachments", cv, "id = ?", new String[]
- { Long.toString(attachmentId) });
- }
-
- if (attachmentId != -1 && tempAttachmentFile != null) {
- File attachmentFile = new File(attachmentDirectory, Long.toString(attachmentId));
- tempAttachmentFile.renameTo(attachmentFile);
- contentUri = AttachmentProvider.getAttachmentUri(
- mAccount,
- attachmentId);
- if (MimeUtil.isMessage(attachment.getMimeType())) {
- attachment.setBody(new LocalAttachmentMessageBody(
- contentUri, mApplication));
- } else {
- attachment.setBody(new LocalAttachmentBody(
- contentUri, mApplication));
- }
- ContentValues cv = new ContentValues();
- cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
- db.update("attachments", cv, "id = ?", new String[]
- { Long.toString(attachmentId) });
- }
-
- /* The message has attachment with Content-ID */
- if (contentId != null && contentUri != null) {
- Cursor cursor = db.query("messages", new String[]
- { "html_content" }, "id = ?", new String[]
- { Long.toString(messageId) }, null, null, null);
- try {
- if (cursor.moveToNext()) {
- String htmlContent = cursor.getString(0);
-
- if (htmlContent != null) {
- String newHtmlContent = htmlContent.replaceAll(
- Pattern.quote("cid:" + contentId),
- contentUri.toString());
-
- ContentValues cv = new ContentValues();
- cv.put("html_content", newHtmlContent);
- db.update("messages", cv, "id = ?", new String[]
- { Long.toString(messageId) });
- }
- }
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
-
- if (attachmentId != -1 && attachment instanceof LocalAttachmentBodyPart) {
- ((LocalAttachmentBodyPart) attachment).setAttachmentId(attachmentId);
- }
- return null;
- } catch (MessagingException e) {
- throw new WrappedException(e);
- } catch (IOException e) {
- throw new WrappedException(e);
- }
- }
- });
- } catch (WrappedException e) {
- final Throwable cause = e.getCause();
- if (cause instanceof IOException) {
- throw (IOException) cause;
- }
-
- throw (MessagingException) cause;
- }
- }
-
- /**
- * Changes the stored uid of the given message (using it's internal id as a key) to
- * the uid in the message.
- * @param message
- * @throws com.fsck.k9.mail.MessagingException
- */
- public void changeUid(final LocalMessage message) throws MessagingException {
- open(OPEN_MODE_RW);
- final ContentValues cv = new ContentValues();
- cv.put("uid", message.getUid());
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- db.update("messages", cv, "id = ?", new String[]
- { Long.toString(message.mId) });
- return null;
- }
- });
-
- //TODO: remove this once the UI code exclusively uses the database id
- notifyChange();
- }
-
- @Override
- public void setFlags(final Message[] messages, final Flag[] flags, final boolean value)
- throws MessagingException {
- open(OPEN_MODE_RW);
-
- // Use one transaction to set all flags
- try {
- database.execute(true, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
- UnavailableStorageException {
-
- for (Message message : messages) {
- try {
- message.setFlags(flags, value);
- } catch (MessagingException e) {
- Log.e(K9.LOG_TAG, "Something went wrong while setting flag", e);
- }
- }
-
- return null;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public void setFlags(Flag[] flags, boolean value)
- throws MessagingException {
- open(OPEN_MODE_RW);
- for (Message message : getMessages(null)) {
- message.setFlags(flags, value);
- }
- }
-
- @Override
- public String getUidFromMessageId(Message message) throws MessagingException {
- throw new MessagingException("Cannot call getUidFromMessageId on LocalFolder");
- }
-
- public void clearMessagesOlderThan(long cutoff) throws MessagingException {
- open(OPEN_MODE_RO);
-
- Message[] messages = LocalStore.this.getMessages(
- null,
- this,
- "SELECT " + GET_MESSAGES_COLS +
- "FROM messages " +
- "LEFT JOIN threads ON (threads.message_id = messages.id) " +
- "WHERE (empty IS NULL OR empty != 1) AND " +
- "(folder_id = ? and date < ?)",
- new String[] {
- Long.toString(mFolderId), Long.toString(cutoff)
- });
-
- for (Message message : messages) {
- message.destroy();
- }
-
- notifyChange();
- }
-
- public void clearAllMessages() throws MessagingException {
- final String[] folderIdArg = new String[] { Long.toString(mFolderId) };
-
- open(OPEN_MODE_RO);
-
- try {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- try {
- // Get UIDs for all messages to delete
- Cursor cursor = db.query("messages", new String[] { "uid" },
- "folder_id = ? AND (empty IS NULL OR empty != 1)",
- folderIdArg, null, null, null);
-
- try {
- // Delete attachments of these messages
- while (cursor.moveToNext()) {
- deleteAttachments(cursor.getString(0));
- }
- } finally {
- cursor.close();
- }
-
- // Delete entries in 'threads' and 'messages'
- db.execSQL("DELETE FROM threads WHERE message_id IN " +
- "(SELECT id FROM messages WHERE folder_id = ?)", folderIdArg);
- db.execSQL("DELETE FROM messages WHERE folder_id = ?", folderIdArg);
-
- return null;
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
-
- notifyChange();
-
- setPushState(null);
- setLastPush(0);
- setLastChecked(0);
- setVisibleLimit(mAccount.getDisplayCount());
- }
-
- @Override
- public void delete(final boolean recurse) throws MessagingException {
- try {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- try {
- // We need to open the folder first to make sure we've got it's id
- open(OPEN_MODE_RO);
- Message[] messages = getMessages(null);
- for (Message message : messages) {
- deleteAttachments(message.getUid());
- }
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
- db.execSQL("DELETE FROM folders WHERE id = ?", new Object[]
- { Long.toString(mFolderId), });
- return null;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (o instanceof LocalFolder) {
- return ((LocalFolder)o).mName.equals(mName);
- }
- return super.equals(o);
- }
-
- @Override
- public int hashCode() {
- return mName.hashCode();
- }
-
- private void deleteAttachments(final long messageId) throws MessagingException {
- open(OPEN_MODE_RW);
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- Cursor attachmentsCursor = null;
- try {
- String accountUuid = mAccount.getUuid();
- Context context = mApplication;
-
- // Get attachment IDs
- String[] whereArgs = new String[] { Long.toString(messageId) };
- attachmentsCursor = db.query("attachments", new String[] { "id" },
- "message_id = ?", whereArgs, null, null, null);
-
- final File attachmentDirectory = StorageManager.getInstance(mApplication)
- .getAttachmentDirectory(uUid, database.getStorageProviderId());
-
- while (attachmentsCursor.moveToNext()) {
- String attachmentId = Long.toString(attachmentsCursor.getLong(0));
- try {
- // Delete stored attachment
- File file = new File(attachmentDirectory, attachmentId);
- if (file.exists()) {
- file.delete();
- }
-
- // Delete thumbnail file
- AttachmentProvider.deleteThumbnail(context, accountUuid,
- attachmentId);
- } catch (Exception e) { /* ignore */ }
- }
-
- // Delete attachment metadata from the database
- db.delete("attachments", "message_id = ?", whereArgs);
- } finally {
- Utility.closeQuietly(attachmentsCursor);
- }
- return null;
- }
- });
- }
-
- private void deleteAttachments(final String uid) throws MessagingException {
- open(OPEN_MODE_RW);
- try {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
- Cursor messagesCursor = null;
- try {
- messagesCursor = db.query("messages", new String[]
- { "id" }, "folder_id = ? AND uid = ?", new String[]
- { Long.toString(mFolderId), uid }, null, null, null);
- while (messagesCursor.moveToNext()) {
- long messageId = messagesCursor.getLong(0);
- deleteAttachments(messageId);
-
- }
- } catch (MessagingException e) {
- throw new WrappedException(e);
- } finally {
- Utility.closeQuietly(messagesCursor);
- }
- return null;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
-
- @Override
- public boolean isInTopGroup() {
- return mInTopGroup;
- }
-
- public void setInTopGroup(boolean inTopGroup) throws MessagingException {
- mInTopGroup = inTopGroup;
- updateFolderColumn("top_group", mInTopGroup ? 1 : 0);
- }
-
- public Integer getLastUid() {
- return mLastUid;
- }
-
- /**
- *
Fetches the most recent numeric UID value in this folder. This is used by
- * {@link com.fsck.k9.controller.MessagingController#shouldNotifyForMessage} to see if messages being
- * fetched are new and unread. Messages are "new" if they have a UID higher than the most recent UID prior
- * to synchronization.
- *
- *
This only works for protocols with numeric UIDs (like IMAP). For protocols with
- * alphanumeric UIDs (like POP), this method quietly fails and shouldNotifyForMessage() will
- * always notify for unread messages.
- *
- *
Once Issue 1072 has been fixed, this method and shouldNotifyForMessage() should be
- * updated to use internal dates rather than UIDs to determine new-ness. While this doesn't
- * solve things for POP (which doesn't have internal dates), we can likely use this as a
- * framework to examine send date in lieu of internal date.
- * @throws MessagingException
- */
- public void updateLastUid() throws MessagingException {
- Integer lastUid = database.execute(false, new DbCallback() {
- @Override
- public Integer doDbWork(final SQLiteDatabase db) {
- Cursor cursor = null;
- try {
- open(OPEN_MODE_RO);
- cursor = db.rawQuery("SELECT MAX(uid) FROM messages WHERE folder_id=?", new String[] { Long.toString(mFolderId) });
- if (cursor.getCount() > 0) {
- cursor.moveToFirst();
- return cursor.getInt(0);
- }
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Unable to updateLastUid: ", e);
- } finally {
- Utility.closeQuietly(cursor);
- }
- return null;
- }
- });
- if (K9.DEBUG)
- Log.d(K9.LOG_TAG, "Updated last UID for folder " + mName + " to " + lastUid);
- mLastUid = lastUid;
- }
-
- public Long getOldestMessageDate() throws MessagingException {
- return database.execute(false, new DbCallback() {
- @Override
- public Long doDbWork(final SQLiteDatabase db) {
- Cursor cursor = null;
- try {
- open(OPEN_MODE_RO);
- cursor = db.rawQuery("SELECT MIN(date) FROM messages WHERE folder_id=?", new String[] { Long.toString(mFolderId) });
- if (cursor.getCount() > 0) {
- cursor.moveToFirst();
- return cursor.getLong(0);
- }
- } catch (Exception e) {
- Log.e(K9.LOG_TAG, "Unable to fetch oldest message date: ", e);
- } finally {
- Utility.closeQuietly(cursor);
- }
- return null;
- }
- });
- }
-
- private ThreadInfo doMessageThreading(SQLiteDatabase db, Message message)
- throws MessagingException {
- long rootId = -1;
- long parentId = -1;
-
- String messageId = message.getMessageId();
-
- // If there's already an empty message in the database, update that
- ThreadInfo msgThreadInfo = getThreadInfo(db, messageId, true);
-
- // Get the message IDs from the "References" header line
- String[] referencesArray = message.getHeader("References");
- List messageIds = null;
- if (referencesArray != null && referencesArray.length > 0) {
- messageIds = Utility.extractMessageIds(referencesArray[0]);
- }
-
- // Append the first message ID from the "In-Reply-To" header line
- String[] inReplyToArray = message.getHeader("In-Reply-To");
- String inReplyTo = null;
- if (inReplyToArray != null && inReplyToArray.length > 0) {
- inReplyTo = Utility.extractMessageId(inReplyToArray[0]);
- if (inReplyTo != null) {
- if (messageIds == null) {
- messageIds = new ArrayList(1);
- messageIds.add(inReplyTo);
- } else if (!messageIds.contains(inReplyTo)) {
- messageIds.add(inReplyTo);
- }
- }
- }
-
- if (messageIds == null) {
- // This is not a reply, nothing to do for us.
- return (msgThreadInfo != null) ?
- msgThreadInfo : new ThreadInfo(-1, -1, messageId, -1, -1);
- }
-
- for (String reference : messageIds) {
- ThreadInfo threadInfo = getThreadInfo(db, reference, false);
-
- if (threadInfo == null) {
- // Create placeholder message in 'messages' table
- ContentValues cv = new ContentValues();
- cv.put("message_id", reference);
- cv.put("folder_id", mFolderId);
- cv.put("empty", 1);
-
- long newMsgId = db.insert("messages", null, cv);
-
- // Create entry in 'threads' table
- cv.clear();
- cv.put("message_id", newMsgId);
- if (rootId != -1) {
- cv.put("root", rootId);
- }
- if (parentId != -1) {
- cv.put("parent", parentId);
- }
-
- parentId = db.insert("threads", null, cv);
- if (rootId == -1) {
- rootId = parentId;
- }
- } else {
- if (rootId != -1 && threadInfo.rootId == -1 && rootId != threadInfo.threadId) {
- // We found an existing root container that is not
- // the root of our current path (References).
- // Connect it to the current parent.
-
- // Let all children know who's the new root
- ContentValues cv = new ContentValues();
- cv.put("root", rootId);
- db.update("threads", cv, "root = ?",
- new String[] { Long.toString(threadInfo.threadId) });
-
- // Connect the message to the current parent
- cv.put("parent", parentId);
- db.update("threads", cv, "id = ?",
- new String[] { Long.toString(threadInfo.threadId) });
- } else {
- rootId = (threadInfo.rootId == -1) ?
- threadInfo.threadId : threadInfo.rootId;
- }
- parentId = threadInfo.threadId;
- }
- }
-
- //TODO: set in-reply-to "link" even if one already exists
-
- long threadId;
- long msgId;
- if (msgThreadInfo != null) {
- threadId = msgThreadInfo.threadId;
- msgId = msgThreadInfo.msgId;
- } else {
- threadId = -1;
- msgId = -1;
- }
-
- return new ThreadInfo(threadId, msgId, messageId, rootId, parentId);
- }
-
- public List extractNewMessages(final List messages)
- throws MessagingException {
-
- try {
- return database.execute(false, new DbCallback>() {
- @Override
- public List doDbWork(final SQLiteDatabase db) throws WrappedException {
- try {
- open(OPEN_MODE_RW);
- } catch (MessagingException e) {
- throw new WrappedException(e);
- }
-
- List result = new ArrayList();
-
- List selectionArgs = new ArrayList();
- Set existingMessages = new HashSet();
- int start = 0;
-
- while (start < messages.size()) {
- StringBuilder selection = new StringBuilder();
-
- selection.append("folder_id = ? AND UID IN (");
- selectionArgs.add(Long.toString(mFolderId));
-
- int count = Math.min(messages.size() - start, UID_CHECK_BATCH_SIZE);
-
- for (int i = start, end = start + count; i < end; i++) {
- if (i > start) {
- selection.append(",?");
- } else {
- selection.append("?");
- }
-
- selectionArgs.add(messages.get(i).getUid());
- }
-
- selection.append(")");
-
- Cursor cursor = db.query("messages", UID_CHECK_PROJECTION,
- selection.toString(), selectionArgs.toArray(EMPTY_STRING_ARRAY),
- null, null, null);
-
- try {
- while (cursor.moveToNext()) {
- String uid = cursor.getString(0);
- existingMessages.add(uid);
- }
- } finally {
- Utility.closeQuietly(cursor);
- }
-
- for (int i = start, end = start + count; i < end; i++) {
- Message message = messages.get(i);
- if (!existingMessages.contains(message.getUid())) {
- result.add(message);
- }
- }
-
- existingMessages.clear();
- selectionArgs.clear();
- start += count;
- }
-
- return result;
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- }
- }
-
- 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;
-
- private long mThreadId;
- private long mRootId;
-
- public LocalMessage() {
- }
-
- LocalMessage(String uid, Folder folder) {
- this.mUid = uid;
- this.mFolder = folder;
- }
-
- private 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(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);
- }
-
- 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;
- }
-
- @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 {
- database.execute(true, new DbCallback() {
- @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", 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();
- }
-
- 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 {
- database.execute(true, new DbCallback() {
- @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);
-
- notifyChange();
- }
-
- /*
- * Completely remove a message from the local database
- *
- * TODO: document how this updates the thread structure
- */
- @Override
- public void destroy() throws MessagingException {
- try {
- database.execute(true, new DbCallback() {
- @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();
- }
-
- 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 messages = 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) 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 getHeaderNames() throws UnavailableStorageException {
- if (!mHeadersLoaded)
- loadHeaders();
- return super.getHeaderNames();
- }
-
- @Override
- public LocalMessage clone() {
- LocalMessage message = new LocalMessage();
- 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;
- }
- }
-
- static 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;
- }
- }
-
public LockableDatabase getDatabase() {
return database;
}
- private void notifyChange() {
+ void notifyChange() {
Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + uUid + "/messages");
mContentResolver.notifyChange(uri, null);
}
diff --git a/src/com/fsck/k9/mail/store/local/ThreadInfo.java b/src/com/fsck/k9/mail/store/local/ThreadInfo.java
new file mode 100644
index 000000000..4115c16d6
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/ThreadInfo.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java
index bb80239a2..155661e97 100644
--- a/src/com/fsck/k9/mail/transport/SmtpTransport.java
+++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java
@@ -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.local.LocalStore.LocalMessage;
+import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.net.ssl.SslHelper;
import javax.net.ssl.SSLException;
diff --git a/src/com/fsck/k9/provider/MessageProvider.java b/src/com/fsck/k9/provider/MessageProvider.java
index 343f9fbe6..6ce34baf3 100644
--- a/src/com/fsck/k9/provider/MessageProvider.java
+++ b/src/com/fsck/k9/provider/MessageProvider.java
@@ -32,6 +32,7 @@ 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.local.LocalMessage;
import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.search.SearchAccount;
@@ -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 {
@Override
diff --git a/src/com/fsck/k9/search/SqlQueryBuilder.java b/src/com/fsck/k9/search/SqlQueryBuilder.java
index 224c0b901..1b6ecf6a2 100644
--- a/src/com/fsck/k9/search/SqlQueryBuilder.java
+++ b/src/com/fsck/k9/search/SqlQueryBuilder.java
@@ -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.local.LocalFolder;
import com.fsck.k9.mail.store.local.LocalStore;
-import com.fsck.k9.mail.store.local.LocalStore.LocalFolder;
import com.fsck.k9.search.SearchSpecification.Attribute;
import com.fsck.k9.search.SearchSpecification.SearchCondition;
import com.fsck.k9.search.SearchSpecification.Searchfield;
diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java
index f791f030c..f9e1cdda1 100644
--- a/src/com/fsck/k9/view/SingleMessageView.java
+++ b/src/com/fsck/k9/view/SingleMessageView.java
@@ -56,7 +56,7 @@ 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.local.LocalAttachmentBodyPart;
-import com.fsck.k9.mail.store.local.LocalStore.LocalMessage;
+import com.fsck.k9.mail.store.local.LocalMessage;
import com.fsck.k9.provider.AttachmentProvider.AttachmentProviderColumns;
import org.apache.commons.io.IOUtils;
From eced036d69926dab1f1baf7f1dc77f1d9367e71e Mon Sep 17 00:00:00 2001
From: Christian Frommeyer
Date: Sun, 7 Sep 2014 16:18:06 +0200
Subject: [PATCH 05/39] Extracting Database Setup Schema definition form
LocalStore.
---
.../fsck/k9/mail/store/local/LocalStore.java | 571 +----------------
.../store/local/StoreSchemaDefinition.java | 599 ++++++++++++++++++
2 files changed, 600 insertions(+), 570 deletions(-)
create mode 100644 src/com/fsck/k9/mail/store/local/StoreSchemaDefinition.java
diff --git a/src/com/fsck/k9/mail/store/local/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
index 7ede93e36..3442aa988 100644
--- a/src/com/fsck/k9/mail/store/local/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -20,14 +20,12 @@ import android.content.Context;
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.R;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.StringUtils;
import com.fsck.k9.helper.Utility;
@@ -43,7 +41,6 @@ import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
import com.fsck.k9.mail.store.LockableDatabase.SchemaDefinition;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
-import com.fsck.k9.provider.AttachmentProvider;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.MessageColumns;
import com.fsck.k9.search.LocalSearch;
@@ -157,7 +154,7 @@ public class LocalStore extends Store implements Serializable {
*/
public LocalStore(final Account account, final Application application) throws MessagingException {
super(account);
- database = new LockableDatabase(application, account.getUuid(), new StoreSchemaDefinition());
+ database = new LockableDatabase(application, account.getUuid(), new StoreSchemaDefinition(this));
mApplication = application;
mContentResolver = application.getContentResolver();
@@ -175,572 +172,6 @@ public class LocalStore extends Store implements Serializable {
return Preferences.getPreferences(mApplication).getPreferences();
}
- private class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition {
- @Override
- public int getVersion() {
- return 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(), DB_VERSION));
-
- AttachmentProvider.clear(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 = 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 = getPreferences().edit();
-
- List extends Folder > folders = 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(LocalStore.this, "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(LocalStore.this, 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(LocalStore.this, mAccount.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 extraFlags = new ArrayList();
-
- 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", serializeFlags(extraFlags.toArray(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[] { getAccount().getInboxFolderName() });
- }
- }
-
- db.setVersion(DB_VERSION);
-
- db.setTransactionSuccessful();
- } finally {
- db.endTransaction();
- }
-
- if (db.getVersion() != 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 (mAccount.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(uUid + "." + name + ".displayMode", displayClass.name()));
- syncClass = Folder.FolderClass.valueOf(prefs.getString(uUid + "." + name + ".syncMode", syncClass.name()));
- pushClass = Folder.FolderClass.valueOf(prefs.getString(uUid + "." + name + ".pushMode", pushClass.name()));
- inTopGroup = prefs.getBoolean(uUid + "." + name + ".inTopGroup", inTopGroup);
- integrate = prefs.getBoolean(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 });
-
- }
- }
-
-
public long getSize() throws UnavailableStorageException {
final StorageManager storageManager = StorageManager.getInstance(mApplication);
diff --git a/src/com/fsck/k9/mail/store/local/StoreSchemaDefinition.java b/src/com/fsck/k9/mail/store/local/StoreSchemaDefinition.java
new file mode 100644
index 000000000..277ee719e
--- /dev/null
+++ b/src/com/fsck/k9/mail/store/local/StoreSchemaDefinition.java
@@ -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 extraFlags = new ArrayList();
+
+ 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 });
+
+ }
+}
\ No newline at end of file
From 9dba60c997cbeffb4605b79ce3e6bdf762a7c39d Mon Sep 17 00:00:00 2001
From: Christian Frommeyer
Date: Sun, 7 Sep 2014 16:58:46 +0200
Subject: [PATCH 06/39] Some minor code cleanings and logging for
LockableDatabase
---
.../fsck/k9/mail/store/LockableDatabase.java | 45 +++++++++----------
1 file changed, 21 insertions(+), 24 deletions(-)
diff --git a/src/com/fsck/k9/mail/store/LockableDatabase.java b/src/com/fsck/k9/mail/store/LockableDatabase.java
index 81138d6e8..00b3f0add 100644
--- a/src/com/fsck/k9/mail/store/LockableDatabase.java
+++ b/src/com/fsck/k9/mail/store/LockableDatabase.java
@@ -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 null.
@@ -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));
From d467dca32c5807a86a2bf6f7eb270acccfda1b48 Mon Sep 17 00:00:00 2001
From: Tobias Baum
Date: Sun, 14 Sep 2014 11:05:55 +0200
Subject: [PATCH 07/39] Added testcases for parsing and removed unused methods
for parsing
---
.../fsck/k9/mail/internet/MimeMessage.java | 42 +---
.../mail/internet/MimeMessageParseTest.java | 236 ++++++++++++++++++
2 files changed, 247 insertions(+), 31 deletions(-)
create mode 100644 tests/src/com/fsck/k9/mail/internet/MimeMessageParseTest.java
diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java
index 2f69cba39..ce0ea76d6 100644
--- a/src/com/fsck/k9/mail/internet/MimeMessage.java
+++ b/src/com/fsck/k9/mail/internet/MimeMessage.java
@@ -25,6 +25,7 @@ import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.MimeConfig;
import org.apache.james.mime4j.util.MimeUtil;
+import com.fsck.k9.BuildConfig;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
@@ -62,20 +63,6 @@ public class MimeMessage extends Message {
}
- /**
- * Parse the given InputStream using Apache Mime4J to build a MimeMessage.
- * Nested messages will not be recursively parsed.
- *
- * @param in
- * @throws IOException
- * @throws MessagingException
- *
- * @see #MimeMessage(InputStream in, boolean recurse)
- */
- public MimeMessage(InputStream in) throws IOException, MessagingException {
- parse(in);
- }
-
/**
* Parse the given InputStream using Apache Mime4J to build a MimeMessage.
*
@@ -88,11 +75,15 @@ public class MimeMessage extends Message {
parse(in, recurse);
}
- protected void parse(InputStream in) throws IOException, MessagingException {
+ /**
+ * Parse the given InputStream using Apache Mime4J to build a MimeMessage.
+ * Does not recurse through nested bodyparts.
+ */
+ public final void parse(InputStream in) throws IOException, MessagingException {
parse(in, false);
}
- protected void parse(InputStream in, boolean recurse) throws IOException, MessagingException {
+ private void parse(InputStream in, boolean recurse) throws IOException, MessagingException {
mHeader.clear();
mFrom = null;
mTo = null;
@@ -121,8 +112,8 @@ public class MimeMessage extends Message {
try {
parser.parse(new EOLConvertingInputStream(in));
} catch (MimeException me) {
+ //TODO wouldn't a MessagingException be better?
throw new Error(me);
-
}
}
@@ -482,7 +473,7 @@ public class MimeMessage extends Message {
}
}
- class MimeMessageBuilder implements ContentHandler {
+ private class MimeMessageBuilder implements ContentHandler {
private final LinkedList
*/
+ @Override
public abstract Message clone();
+ @Override
public abstract void setUsing7bitTransport() throws MessagingException;
}
diff --git a/src/com/fsck/k9/mail/MessagingException.java b/src/com/fsck/k9/mail/MessagingException.java
index d33014603..bdb24ab42 100644
--- a/src/com/fsck/k9/mail/MessagingException.java
+++ b/src/com/fsck/k9/mail/MessagingException.java
@@ -4,7 +4,7 @@ package com.fsck.k9.mail;
public class MessagingException extends Exception {
public static final long serialVersionUID = -1;
- boolean permanentFailure = false;
+ private boolean permanentFailure = false;
public MessagingException(String message) {
super(message);
diff --git a/src/com/fsck/k9/mail/Multipart.java b/src/com/fsck/k9/mail/Multipart.java
index d971d18a5..7e397f51f 100644
--- a/src/com/fsck/k9/mail/Multipart.java
+++ b/src/com/fsck/k9/mail/Multipart.java
@@ -11,11 +11,11 @@ import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
public abstract class Multipart implements CompositeBody {
- protected Part mParent;
+ private Part mParent;
protected ArrayList mParts = new ArrayList();
- protected String mContentType;
+ private String mContentType;
public void addBodyPart(BodyPart part) {
mParts.add(part);
diff --git a/src/com/fsck/k9/mail/internet/MimeBodyPart.java b/src/com/fsck/k9/mail/internet/MimeBodyPart.java
index 1c031df17..c4bca428a 100644
--- a/src/com/fsck/k9/mail/internet/MimeBodyPart.java
+++ b/src/com/fsck/k9/mail/internet/MimeBodyPart.java
@@ -20,8 +20,8 @@ import org.apache.james.mime4j.util.MimeUtil;
* Message.
*/
public class MimeBodyPart extends BodyPart {
- protected final MimeHeader mHeader = new MimeHeader();
- protected Body mBody;
+ private final MimeHeader mHeader = new MimeHeader();
+ private Body mBody;
public MimeBodyPart() throws MessagingException {
this(null);
@@ -38,30 +38,36 @@ public class MimeBodyPart extends BodyPart {
setBody(body);
}
- protected String getFirstHeader(String name) {
+ private String getFirstHeader(String name) {
return mHeader.getFirstHeader(name);
}
+ @Override
public void addHeader(String name, String value) throws MessagingException {
mHeader.addHeader(name, value);
}
+ @Override
public void setHeader(String name, String value) {
mHeader.setHeader(name, value);
}
+ @Override
public String[] getHeader(String name) throws MessagingException {
return mHeader.getHeader(name);
}
+ @Override
public void removeHeader(String name) throws MessagingException {
mHeader.removeHeader(name);
}
+ @Override
public Body getBody() {
return mBody;
}
+ @Override
public void setBody(Body body) throws MessagingException {
this.mBody = body;
if (body instanceof Multipart) {
@@ -93,15 +99,18 @@ public class MimeBodyPart extends BodyPart {
setHeader(MimeHeader.HEADER_CONTENT_TRANSFER_ENCODING, encoding);
}
+ @Override
public String getContentType() throws MessagingException {
String contentType = getFirstHeader(MimeHeader.HEADER_CONTENT_TYPE);
return (contentType == null) ? "text/plain" : contentType;
}
+ @Override
public String getDisposition() throws MessagingException {
return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
}
+ @Override
public String getContentId() throws MessagingException {
String contentId = getFirstHeader(MimeHeader.HEADER_CONTENT_ID);
if (contentId == null) {
@@ -116,10 +125,12 @@ public class MimeBodyPart extends BodyPart {
contentId;
}
+ @Override
public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null);
}
+ @Override
public boolean isMimeType(String mimeType) throws MessagingException {
return getMimeType().equalsIgnoreCase(mimeType);
}
@@ -127,6 +138,7 @@ public class MimeBodyPart extends BodyPart {
/**
* Write the MimeMessage out in MIME format.
*/
+ @Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
mHeader.writeTo(out);
diff --git a/src/com/fsck/k9/mail/internet/MimeHeader.java b/src/com/fsck/k9/mail/internet/MimeHeader.java
index 5175f4ad2..dd6445755 100644
--- a/src/com/fsck/k9/mail/internet/MimeHeader.java
+++ b/src/com/fsck/k9/mail/internet/MimeHeader.java
@@ -36,7 +36,7 @@ public class MimeHeader {
HEADER_ANDROID_ATTACHMENT_STORE_DATA
};
- protected ArrayList mFields = new ArrayList();
+ private ArrayList mFields = new ArrayList();
private String mCharset = null;
public void clear() {
@@ -119,7 +119,7 @@ public class MimeHeader {
}
// encode non printable characters except LF/CR/TAB codes.
- public boolean hasToBeEncoded(String text) {
+ private boolean hasToBeEncoded(String text) {
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
if ((c < 0x20 || 0x7e < c) && // non printable
@@ -131,10 +131,10 @@ public class MimeHeader {
return false;
}
- static class Field {
- final String name;
+ private static class Field {
+ private final String name;
- final String value;
+ private final String value;
public Field(String name, String value) {
this.name = name;
@@ -153,6 +153,7 @@ public class MimeHeader {
mCharset = charset;
}
+ @Override
public MimeHeader clone() {
MimeHeader header = new MimeHeader();
header.mCharset = mCharset;
diff --git a/src/com/fsck/k9/mail/internet/MimeMessage.java b/src/com/fsck/k9/mail/internet/MimeMessage.java
index ce0ea76d6..75fa217d8 100644
--- a/src/com/fsck/k9/mail/internet/MimeMessage.java
+++ b/src/com/fsck/k9/mail/internet/MimeMessage.java
@@ -25,7 +25,6 @@ import org.apache.james.mime4j.stream.Field;
import org.apache.james.mime4j.stream.MimeConfig;
import org.apache.james.mime4j.util.MimeUtil;
-import com.fsck.k9.BuildConfig;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.BodyPart;
@@ -42,7 +41,7 @@ import com.fsck.k9.K9;
* RFC 2045 style headers.
*/
public class MimeMessage extends Message {
- protected MimeHeader mHeader = new MimeHeader();
+ private MimeHeader mHeader = new MimeHeader();
protected Address[] mFrom;
protected Address[] mTo;
protected Address[] mCc;
@@ -50,13 +49,13 @@ public class MimeMessage extends Message {
protected Address[] mReplyTo;
protected String mMessageId;
- protected String[] mReferences;
- protected String[] mInReplyTo;
+ private String[] mReferences;
+ private String[] mInReplyTo;
- protected Date mSentDate;
- protected SimpleDateFormat mDateFormat;
+ private Date mSentDate;
+ private SimpleDateFormat mDateFormat;
- protected Body mBody;
+ private Body mBody;
protected int mSize;
public MimeMessage() {
@@ -168,20 +167,25 @@ public class MimeMessage extends Message {
return (contentType == null) ? "text/plain" : contentType;
}
+ @Override
public String getDisposition() throws MessagingException {
return getFirstHeader(MimeHeader.HEADER_CONTENT_DISPOSITION);
}
+ @Override
public String getContentId() throws MessagingException {
return null;
}
+ @Override
public String getMimeType() throws MessagingException {
return MimeUtility.getHeaderParameter(getContentType(), null);
}
+ @Override
public boolean isMimeType(String mimeType) throws MessagingException {
return getMimeType().equalsIgnoreCase(mimeType);
}
+ @Override
public int getSize() {
return mSize;
}
@@ -410,7 +414,7 @@ public class MimeMessage extends Message {
}
}
- protected String getFirstHeader(String name) {
+ private String getFirstHeader(String name) {
return mHeader.getFirstHeader(name);
}
@@ -439,6 +443,7 @@ public class MimeMessage extends Message {
return mHeader.getHeaderNames();
}
+ @Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
@@ -450,6 +455,7 @@ public class MimeMessage extends Message {
}
}
+ @Override
public InputStream getInputStream() throws MessagingException {
return null;
}
@@ -486,6 +492,7 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void startMessage() {
if (stack.isEmpty()) {
stack.addFirst(MimeMessage.this);
@@ -501,19 +508,23 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void endMessage() {
expect(MimeMessage.class);
stack.removeFirst();
}
+ @Override
public void startHeader() {
expect(Part.class);
}
+ @Override
public void endHeader() {
expect(Part.class);
}
+ @Override
public void startMultipart(BodyDescriptor bd) {
expect(Part.class);
@@ -527,6 +538,7 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void body(BodyDescriptor bd, InputStream in) throws IOException {
expect(Part.class);
try {
@@ -538,10 +550,12 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void endMultipart() {
stack.removeFirst();
}
+ @Override
public void startBodyPart() {
expect(MimeMultipart.class);
@@ -554,11 +568,13 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void endBodyPart() {
expect(BodyPart.class);
stack.removeFirst();
}
+ @Override
public void preamble(InputStream is) throws IOException {
expect(MimeMultipart.class);
StringBuilder sb = new StringBuilder();
@@ -569,9 +585,11 @@ public class MimeMessage extends Message {
((MimeMultipart)stack.peek()).setPreamble(sb.toString());
}
+ @Override
public void epilogue(InputStream is) throws IOException {
}
+ @Override
public void raw(InputStream is) throws IOException {
throw new UnsupportedOperationException("Not supported");
}
@@ -621,14 +639,17 @@ public class MimeMessage extends Message {
return message;
}
+ @Override
public long getId() {
return Long.parseLong(mUid); //or maybe .mMessageId?
}
+ @Override
public String getPreview() {
return "";
}
+ @Override
public boolean hasAttachments() {
return false;
}
diff --git a/src/com/fsck/k9/mail/internet/MimeMultipart.java b/src/com/fsck/k9/mail/internet/MimeMultipart.java
index e00d4da3e..a63ebad04 100644
--- a/src/com/fsck/k9/mail/internet/MimeMultipart.java
+++ b/src/com/fsck/k9/mail/internet/MimeMultipart.java
@@ -10,13 +10,11 @@ import java.util.Locale;
import java.util.Random;
public class MimeMultipart extends Multipart {
- protected String mPreamble;
+ private String mPreamble;
- protected String mContentType;
+ private String mContentType;
- protected String mBoundary;
-
- protected String mSubType;
+ private final String mBoundary;
public MimeMultipart() throws MessagingException {
mBoundary = generateBoundary();
@@ -26,7 +24,6 @@ public class MimeMultipart extends Multipart {
public MimeMultipart(String contentType) throws MessagingException {
this.mContentType = contentType;
try {
- mSubType = MimeUtility.getHeaderParameter(contentType, null).split("/")[1];
mBoundary = MimeUtility.getHeaderParameter(contentType, "boundary");
if (mBoundary == null) {
throw new MessagingException("MultiPart does not contain boundary: " + contentType);
@@ -62,10 +59,10 @@ public class MimeMultipart extends Multipart {
}
public void setSubType(String subType) {
- this.mSubType = subType;
mContentType = String.format("multipart/%s; boundary=\"%s\"", subType, mBoundary);
}
+ @Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out), 1024);
@@ -96,6 +93,7 @@ public class MimeMultipart extends Multipart {
writer.flush();
}
+ @Override
public InputStream getInputStream() throws MessagingException {
return null;
}
diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java
index 19770b171..21dad28c7 100644
--- a/src/com/fsck/k9/mail/internet/MimeUtility.java
+++ b/src/com/fsck/k9/mail/internet/MimeUtility.java
@@ -927,11 +927,7 @@ public class MimeUtility {
return s.replaceAll("\r|\n", "");
}
- public static String decode(String s) {
- return decode(s, null);
- }
-
- public static String decode(String s, Message message) {
+ private static String decode(String s, Message message) {
if (s == null) {
return null;
}
@@ -1005,27 +1001,6 @@ public class MimeUtility {
return null;
}
- public static Part findPartByContentId(Part part, String contentId) throws Exception {
- if (part.getBody() instanceof Multipart) {
- Multipart multipart = (Multipart)part.getBody();
- for (BodyPart bodyPart : multipart.getBodyParts()) {
- Part ret = findPartByContentId(bodyPart, contentId);
- if (ret != null) {
- return ret;
- }
- }
- }
- String[] header = part.getHeader(MimeHeader.HEADER_CONTENT_ID);
- if (header != null) {
- for (String s : header) {
- if (s.equals(contentId)) {
- return part;
- }
- }
- }
- return null;
- }
-
/**
* Reads the Part's body and returns a String based on any charset conversion that needed
* to be done. Note, this does not return a text representation of HTML.
@@ -1466,7 +1441,7 @@ public class MimeUtility {
* @throws MessagingException
* In case of an error.
*/
- public static List getViewables(Part part, List attachments) throws MessagingException {
+ private static List getViewables(Part part, List attachments) throws MessagingException {
List viewables = new ArrayList();
Body body = part.getBody();
@@ -2039,7 +2014,7 @@ public class MimeUtility {
return null;
}
- public static Boolean isPartTextualBody(Part part) throws MessagingException {
+ private static Boolean isPartTextualBody(Part part) throws MessagingException {
String disposition = part.getDisposition();
String dispositionType = null;
String dispositionFilename = null;
@@ -2127,7 +2102,7 @@ public class MimeUtility {
*
* @see #MIME_TYPE_REPLACEMENT_MAP
*/
- public static String canonicalizeMimeType(String mimeType) {
+ private static String canonicalizeMimeType(String mimeType) {
String lowerCaseMimeType = mimeType.toLowerCase(Locale.US);
for (String[] mimeTypeMapEntry : MIME_TYPE_REPLACEMENT_MAP) {
if (mimeTypeMapEntry[0].equals(lowerCaseMimeType)) {
diff --git a/src/com/fsck/k9/mail/internet/TextBody.java b/src/com/fsck/k9/mail/internet/TextBody.java
index 2d15b9ab8..ad6e474ac 100644
--- a/src/com/fsck/k9/mail/internet/TextBody.java
+++ b/src/com/fsck/k9/mail/internet/TextBody.java
@@ -16,7 +16,7 @@ public class TextBody implements Body {
*/
private static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
- private String mBody;
+ private final String mBody;
private String mEncoding;
private String mCharset = "UTF-8";
// Length of the message composed (as opposed to quoted). I don't like the name of this variable and am open to
@@ -29,6 +29,7 @@ public class TextBody implements Body {
this.mBody = body;
}
+ @Override
public void writeTo(OutputStream out) throws IOException, MessagingException {
if (mBody != null) {
byte[] bytes = mBody.getBytes(mCharset);
@@ -54,6 +55,7 @@ public class TextBody implements Body {
/**
* Returns an InputStream that reads this body's text.
*/
+ @Override
public InputStream getInputStream() throws MessagingException {
try {
byte[] b;
@@ -68,6 +70,7 @@ public class TextBody implements Body {
}
}
+ @Override
public void setEncoding(String encoding) {
mEncoding = encoding;
}
diff --git a/src/com/fsck/k9/mail/internet/TextBodyBuilder.java b/src/com/fsck/k9/mail/internet/TextBodyBuilder.java
index 96e19e9e9..9627e99e6 100644
--- a/src/com/fsck/k9/mail/internet/TextBodyBuilder.java
+++ b/src/com/fsck/k9/mail/internet/TextBodyBuilder.java
@@ -204,7 +204,7 @@ public class TextBodyBuilder {
return mQuotedTextHtml;
}
- public String textToHtmlFragment(String text) {
+ private String textToHtmlFragment(String text) {
return HtmlConverter.textToHtmlFragment(text);
}
diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index c830ce1e8..2dba608ea 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -1302,6 +1302,7 @@ public class ImapStore extends Store {
protected long getHighestUid() {
try {
ImapSearcher searcher = new ImapSearcher() {
+ @Override
public List search() throws IOException, MessagingException {
return executeSimpleCommand("UID SEARCH *:*");
}
@@ -1351,6 +1352,7 @@ public class ImapStore extends Store {
ImapSearcher searcher = new ImapSearcher() {
+ @Override
public List search() throws IOException, MessagingException {
return executeSimpleCommand(String.format(Locale.US, "UID SEARCH %d:%d%s%s", start, end, dateSearchString, includeDeleted ? "" : " NOT DELETED"));
}
@@ -1361,6 +1363,7 @@ public class ImapStore extends Store {
protected Message[] getMessages(final List mesgSeqs, final boolean includeDeleted, final MessageRetrievalListener listener)
throws MessagingException {
ImapSearcher searcher = new ImapSearcher() {
+ @Override
public List search() throws IOException, MessagingException {
return executeSimpleCommand(String.format("UID SEARCH %s%s", Utility.combine(mesgSeqs.toArray(), ','), includeDeleted ? "" : " NOT DELETED"));
}
@@ -1371,6 +1374,7 @@ public class ImapStore extends Store {
protected Message[] getMessagesFromUids(final List mesgUids, final boolean includeDeleted, final MessageRetrievalListener listener)
throws MessagingException {
ImapSearcher searcher = new ImapSearcher() {
+ @Override
public List search() throws IOException, MessagingException {
return executeSimpleCommand(String.format("UID SEARCH UID %s%s", Utility.combine(mesgUids.toArray(), ','), includeDeleted ? "" : " NOT DELETED"));
}
@@ -2246,6 +2250,7 @@ public class ImapStore extends Store {
// Setup the searcher
final ImapSearcher searcher = new ImapSearcher() {
+ @Override
public List search() throws IOException, MessagingException {
String imapQuery = "UID SEARCH ";
if (requiredFlags != null) {
@@ -2341,12 +2346,12 @@ public class ImapStore extends Store {
* A cacheable class that stores the details for a single IMAP connection.
*/
public static class ImapConnection {
- protected Socket mSocket;
- protected PeekableInputStream mIn;
- protected OutputStream mOut;
- protected ImapResponseParser mParser;
- protected int mNextCommandTag;
- protected Set capabilities = new HashSet();
+ private Socket mSocket;
+ private PeekableInputStream mIn;
+ private OutputStream mOut;
+ private ImapResponseParser mParser;
+ private int mNextCommandTag;
+ private Set capabilities = new HashSet();
private ImapSettings mSettings;
@@ -2946,7 +2951,7 @@ public class ImapStore extends Store {
static class ImapException extends MessagingException {
private static final long serialVersionUID = 3725007182205882394L;
- String mAlertText;
+ private final String mAlertText;
public ImapException(String message, String alertText) {
super(message, true);
@@ -2957,22 +2962,19 @@ public class ImapStore extends Store {
return mAlertText;
}
- public void setAlertText(String alertText) {
- mAlertText = alertText;
- }
}
public class ImapFolderPusher extends ImapFolder implements UntaggedHandler {
- final PushReceiver receiver;
- Thread listeningThread = null;
- final AtomicBoolean stop = new AtomicBoolean(false);
- final AtomicBoolean idling = new AtomicBoolean(false);
- final AtomicBoolean doneSent = new AtomicBoolean(false);
- final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME);
- final AtomicInteger idleFailureCount = new AtomicInteger(0);
- final AtomicBoolean needsPoll = new AtomicBoolean(false);
- List storedUntaggedResponses = new ArrayList();
- TracingWakeLock wakeLock = null;
+ private final PushReceiver receiver;
+ private Thread listeningThread = null;
+ private final AtomicBoolean stop = new AtomicBoolean(false);
+ private final AtomicBoolean idling = new AtomicBoolean(false);
+ private final AtomicBoolean doneSent = new AtomicBoolean(false);
+ private final AtomicInteger delayTime = new AtomicInteger(NORMAL_DELAY_TIME);
+ private final AtomicInteger idleFailureCount = new AtomicInteger(0);
+ private final AtomicBoolean needsPoll = new AtomicBoolean(false);
+ private List storedUntaggedResponses = new ArrayList();
+ private TracingWakeLock wakeLock = null;
public ImapFolderPusher(ImapStore store, String name, PushReceiver nReceiver) {
super(store, name);
@@ -3010,6 +3012,7 @@ public class ImapStore extends Store {
public void start() {
Runnable runner = new Runnable() {
+ @Override
public void run() {
wakeLock.acquire(K9.PUSH_WAKE_LOCK_TIMEOUT);
if (K9.DEBUG)
@@ -3409,6 +3412,7 @@ public class ImapStore extends Store {
}
}
+ @Override
public void handleAsyncUntaggedResponse(ImapResponse response) {
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "Got async response: " + response);
@@ -3459,7 +3463,7 @@ public class ImapStore extends Store {
}
public class ImapPusher implements Pusher {
- final ImapStore mStore;
+ private final ImapStore mStore;
final PushReceiver mReceiver;
private long lastRefresh = -1;
@@ -3470,6 +3474,7 @@ public class ImapStore extends Store {
mReceiver = receiver;
}
+ @Override
public void start(List folderNames) {
stop();
synchronized (folderPushers) {
@@ -3485,6 +3490,7 @@ public class ImapStore extends Store {
}
}
+ @Override
public void refresh() {
synchronized (folderPushers) {
for (ImapFolderPusher folderPusher : folderPushers.values()) {
@@ -3497,6 +3503,7 @@ public class ImapStore extends Store {
}
}
+ @Override
public void stop() {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Requested stop of IMAP pusher");
@@ -3515,14 +3522,17 @@ public class ImapStore extends Store {
}
}
+ @Override
public int getRefreshInterval() {
return (getAccount().getIdleRefreshMinutes() * 60 * 1000);
}
+ @Override
public long getLastRefresh() {
return lastRefresh;
}
+ @Override
public void setLastRefresh(long lastRefresh) {
this.lastRefresh = lastRefresh;
}
diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java
index 2fc4fc5f2..e34354b15 100644
--- a/src/com/fsck/k9/mail/store/Pop3Store.java
+++ b/src/com/fsck/k9/mail/store/Pop3Store.java
@@ -1235,9 +1235,9 @@ public class Pop3Store extends Store {
}
static class Pop3ResponseInputStream extends InputStream {
- InputStream mIn;
- boolean mStartOfLine = true;
- boolean mFinished;
+ private InputStream mIn;
+ private boolean mStartOfLine = true;
+ private boolean mFinished;
public Pop3ResponseInputStream(InputStream in) {
mIn = in;
diff --git a/src/com/fsck/k9/mail/store/StorageManager.java b/src/com/fsck/k9/mail/store/StorageManager.java
index 27dd266d1..68cc216df 100644
--- a/src/com/fsck/k9/mail/store/StorageManager.java
+++ b/src/com/fsck/k9/mail/store/StorageManager.java
@@ -176,12 +176,12 @@ public class StorageManager {
/**
* The root of the denoted storage. Used for mount points checking.
*/
- protected File mRoot;
+ private File mRoot;
/**
* Choosen base directory
*/
- protected File mApplicationDir;
+ private File mApplicationDir;
@Override
public void init(final Context context) {
@@ -258,7 +258,7 @@ public class StorageManager {
public static final String ID = "InternalStorage";
- protected File mRoot;
+ private File mRoot;
@Override
public String getId() {
@@ -328,13 +328,14 @@ public class StorageManager {
/**
* Root of the denoted storage.
*/
- protected File mRoot;
+ private File mRoot;
/**
* Choosen base directory.
*/
- protected File mApplicationDirectory;
+ private File mApplicationDirectory;
+ @Override
public String getId() {
return ID;
}
@@ -392,6 +393,7 @@ public class StorageManager {
public static final String ID = "HtcIncredibleStorage";
+ @Override
public String getId() {
return ID;
}
@@ -428,6 +430,7 @@ public class StorageManager {
public static final String ID = "SamsungGalaxySStorage";
+ @Override
public String getId() {
return ID;
}
diff --git a/src/com/fsck/k9/mail/store/local/LocalMessage.java b/src/com/fsck/k9/mail/store/local/LocalMessage.java
index 8f9dda8d3..f7630a364 100644
--- a/src/com/fsck/k9/mail/store/local/LocalMessage.java
+++ b/src/com/fsck/k9/mail/store/local/LocalMessage.java
@@ -39,7 +39,7 @@ public class LocalMessage extends MimeMessage {
private long mThreadId;
private long mRootId;
- public LocalMessage(LocalStore localStore) {
+ private LocalMessage(LocalStore localStore) {
this.localStore = localStore;
}
diff --git a/src/com/fsck/k9/mail/store/local/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
index 3442aa988..9119831c1 100644
--- a/src/com/fsck/k9/mail/store/local/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -38,7 +38,6 @@ import com.fsck.k9.mail.store.LockableDatabase;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.UnavailableStorageException;
import com.fsck.k9.mail.store.LockableDatabase.DbCallback;
-import com.fsck.k9.mail.store.LockableDatabase.SchemaDefinition;
import com.fsck.k9.mail.store.LockableDatabase.WrappedException;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.provider.EmailProvider;
@@ -348,15 +347,12 @@ public class LocalStore extends Store implements Serializable {
database.recreate();
}
- public void pruneCachedAttachments() throws MessagingException {
- pruneCachedAttachments(false);
- }
-
/**
* Deletes all cached attachments for the entire store.
* @param force
* @throws com.fsck.k9.mail.MessagingException
*/
+ //TODO this method seems to be only called with force=true, simplify accordingly
private void pruneCachedAttachments(final boolean force) throws MessagingException {
database.execute(false, new DbCallback() {
@Override
@@ -418,10 +414,6 @@ public class LocalStore extends Store implements Serializable {
});
}
- public void resetVisibleLimits() throws UnavailableStorageException {
- resetVisibleLimits(mAccount.getDisplayCount());
- }
-
public void resetVisibleLimits(int visibleLimit) throws UnavailableStorageException {
final ContentValues cv = new ContentValues();
cv.put("visible_limit", Integer.toString(visibleLimit));
diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java
index 8c6d178b4..eb381bd83 100644
--- a/src/com/fsck/k9/mail/transport/SmtpTransport.java
+++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java
@@ -33,7 +33,7 @@ public class SmtpTransport extends Transport {
/**
* Decodes a SmtpTransport URI.
- *
+ *
* NOTE: In contrast to ImapStore and Pop3Store, the authType is appended at the end!
*
*
Possible forms:
@@ -183,16 +183,16 @@ public class SmtpTransport extends Transport {
}
- String mHost;
- int mPort;
- String mUsername;
- String mPassword;
- String mClientCertificateAlias;
- AuthType mAuthType;
- ConnectionSecurity mConnectionSecurity;
- Socket mSocket;
- PeekableInputStream mIn;
- OutputStream mOut;
+ private String mHost;
+ private int mPort;
+ private String mUsername;
+ private String mPassword;
+ private String mClientCertificateAlias;
+ private AuthType mAuthType;
+ private ConnectionSecurity mConnectionSecurity;
+ private Socket mSocket;
+ private PeekableInputStream mIn;
+ private OutputStream mOut;
private boolean m8bitEncodingAllowed;
private int mLargestAcceptableMessage;
From 547eb7477479f2e6a0f61e28da9360c7298b480c Mon Sep 17 00:00:00 2001
From: Tobias Baum
Date: Mon, 22 Sep 2014 21:54:02 +0200
Subject: [PATCH 16/39] Changed access to mParts in MimeMultipart so that it
can be private too
---
src/com/fsck/k9/mail/Multipart.java | 2 +-
.../fsck/k9/mail/internet/MimeMultipart.java | 23 +++++++++----------
2 files changed, 12 insertions(+), 13 deletions(-)
diff --git a/src/com/fsck/k9/mail/Multipart.java b/src/com/fsck/k9/mail/Multipart.java
index 7e397f51f..88651485a 100644
--- a/src/com/fsck/k9/mail/Multipart.java
+++ b/src/com/fsck/k9/mail/Multipart.java
@@ -13,7 +13,7 @@ import com.fsck.k9.mail.internet.TextBody;
public abstract class Multipart implements CompositeBody {
private Part mParent;
- protected ArrayList mParts = new ArrayList();
+ private final ArrayList mParts = new ArrayList();
private String mContentType;
diff --git a/src/com/fsck/k9/mail/internet/MimeMultipart.java b/src/com/fsck/k9/mail/internet/MimeMultipart.java
index a63ebad04..124ca5e4c 100644
--- a/src/com/fsck/k9/mail/internet/MimeMultipart.java
+++ b/src/com/fsck/k9/mail/internet/MimeMultipart.java
@@ -71,20 +71,19 @@ public class MimeMultipart extends Multipart {
writer.write("\r\n");
}
- if (mParts.isEmpty()) {
+ if (getBodyParts().isEmpty()) {
writer.write("--");
writer.write(mBoundary);
writer.write("\r\n");
- }
-
- for (int i = 0, count = mParts.size(); i < count; i++) {
- BodyPart bodyPart = mParts.get(i);
- writer.write("--");
- writer.write(mBoundary);
- writer.write("\r\n");
- writer.flush();
- bodyPart.writeTo(out);
- writer.write("\r\n");
+ } else {
+ for (BodyPart bodyPart : getBodyParts()) {
+ writer.write("--");
+ writer.write(mBoundary);
+ writer.write("\r\n");
+ writer.flush();
+ bodyPart.writeTo(out);
+ writer.write("\r\n");
+ }
}
writer.write("--");
@@ -100,7 +99,7 @@ public class MimeMultipart extends Multipart {
@Override
public void setUsing7bitTransport() throws MessagingException {
- for (BodyPart part : mParts) {
+ for (BodyPart part : getBodyParts()) {
part.setUsing7bitTransport();
}
}
From 2be10febf96c65a9899c2e1b1728c437d9d99f67 Mon Sep 17 00:00:00 2001
From: Tobias Baum
Date: Mon, 22 Sep 2014 21:55:08 +0200
Subject: [PATCH 17/39] Added (still rather shallow) missing Javadocs for
classes in BinaryAttachmentBody hierarchy
---
src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java | 4 ++++
src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java | 3 +++
src/com/fsck/k9/mail/store/local/TempFileBody.java | 3 +++
src/com/fsck/k9/mail/store/local/TempFileMessageBody.java | 4 ++++
4 files changed, 14 insertions(+)
diff --git a/src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java b/src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java
index c03fceb0d..27eeb243e 100644
--- a/src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java
+++ b/src/com/fsck/k9/mail/store/local/BinaryAttachmentBody.java
@@ -12,6 +12,10 @@ import com.fsck.k9.mail.Body;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.filter.Base64OutputStream;
+/**
+ * Superclass for attachments that contain binary data.
+ * The source for the data differs for the subclasses.
+ */
public abstract class BinaryAttachmentBody implements Body {
protected String mEncoding;
diff --git a/src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java b/src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java
index 34772be95..faea07a6c 100644
--- a/src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java
+++ b/src/com/fsck/k9/mail/store/local/LocalAttachmentBody.java
@@ -9,6 +9,9 @@ import android.net.Uri;
import com.fsck.k9.mail.MessagingException;
+/**
+ * An attachment whose contents are loaded from an URI.
+ */
public class LocalAttachmentBody extends BinaryAttachmentBody {
private Application mApplication;
private Uri mUri;
diff --git a/src/com/fsck/k9/mail/store/local/TempFileBody.java b/src/com/fsck/k9/mail/store/local/TempFileBody.java
index f093998d8..82720521e 100644
--- a/src/com/fsck/k9/mail/store/local/TempFileBody.java
+++ b/src/com/fsck/k9/mail/store/local/TempFileBody.java
@@ -8,6 +8,9 @@ import java.io.InputStream;
import com.fsck.k9.mail.MessagingException;
+/**
+ * An attachment whose contents are contained in a file.
+ */
public class TempFileBody extends BinaryAttachmentBody {
private final File mFile;
diff --git a/src/com/fsck/k9/mail/store/local/TempFileMessageBody.java b/src/com/fsck/k9/mail/store/local/TempFileMessageBody.java
index 82188f485..1fdb03e4b 100644
--- a/src/com/fsck/k9/mail/store/local/TempFileMessageBody.java
+++ b/src/com/fsck/k9/mail/store/local/TempFileMessageBody.java
@@ -8,6 +8,10 @@ import org.apache.james.mime4j.util.MimeUtil;
import com.fsck.k9.mail.CompositeBody;
import com.fsck.k9.mail.MessagingException;
+/**
+ * An attachment containing a body of type message/rfc822
+ * whose contents are contained in a file.
+ */
public class TempFileMessageBody extends TempFileBody implements CompositeBody {
public TempFileMessageBody(String filename) {
From 46d083bcad8f98816cac00350234135173994e6a Mon Sep 17 00:00:00 2001
From: Art O Cathain
Date: Sun, 28 Sep 2014 10:12:19 +0100
Subject: [PATCH 18/39] fix warnings
---
src/com/fsck/k9/K9.java | 2 +-
src/com/fsck/k9/cache/EmailProviderCache.java | 6 +++---
src/com/fsck/k9/controller/MessagingController.java | 2 +-
src/com/fsck/k9/mail/store/ImapStore.java | 7 ++++---
4 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java
index d45e8f72e..9eead455a 100644
--- a/src/com/fsck/k9/K9.java
+++ b/src/com/fsck/k9/K9.java
@@ -88,7 +88,7 @@ public class K9 extends Application {
*
* @see ApplicationAware
*/
- private static List observers = new ArrayList();
+ private static final List observers = new ArrayList();
/**
* This will be {@code true} once the initialization is complete and {@link #notifyObservers()}
diff --git a/src/com/fsck/k9/cache/EmailProviderCache.java b/src/com/fsck/k9/cache/EmailProviderCache.java
index ba3741f49..972d3abdb 100644
--- a/src/com/fsck/k9/cache/EmailProviderCache.java
+++ b/src/com/fsck/k9/cache/EmailProviderCache.java
@@ -42,9 +42,9 @@ public class EmailProviderCache {
private String mAccountUuid;
- private Map> mMessageCache = new HashMap>();
- private Map> mThreadCache = new HashMap>();
- private Map mHiddenMessageCache = new HashMap();
+ private final Map> mMessageCache = new HashMap>();
+ private final Map> mThreadCache = new HashMap>();
+ private final Map mHiddenMessageCache = new HashMap();
private EmailProviderCache(String accountUuid) {
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index 7b979371b..d71c06997 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -319,7 +319,7 @@ public class MessagingController implements Runnable {
};
// Key is accountNumber
- private ConcurrentHashMap notificationData = new ConcurrentHashMap();
+ private final ConcurrentMap notificationData = new ConcurrentHashMap();
private static final Flag[] SYNC_FLAGS = new Flag[] { Flag.SEEN, Flag.FLAGGED, Flag.ANSWERED, Flag.FORWARDED };
diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index c830ce1e8..1874d91b6 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -31,6 +31,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
+import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -439,7 +440,7 @@ public class ImapStore extends Store {
private static final SimpleDateFormat RFC3501_DATE = new SimpleDateFormat("dd-MMM-yyyy", Locale.US);
- private LinkedList mConnections =
+ private final Deque mConnections =
new LinkedList();
/**
@@ -453,7 +454,7 @@ public class ImapStore extends Store {
* requests. This cache lets us make sure we always reuse, if possible, for a given
* folder name.
*/
- private HashMap mFolderCache = new HashMap();
+ private final Map mFolderCache = new HashMap();
public ImapStore(Account account) throws MessagingException {
super(account);
@@ -3463,7 +3464,7 @@ public class ImapStore extends Store {
final PushReceiver mReceiver;
private long lastRefresh = -1;
- HashMap folderPushers = new HashMap();
+ final Map folderPushers = new HashMap();
public ImapPusher(ImapStore store, PushReceiver receiver) {
mStore = store;
From afb65d5ad7ec2b773480dd674dbc8819e952934d Mon Sep 17 00:00:00 2001
From: Art O Cathain
Date: Sun, 28 Sep 2014 11:39:32 +0100
Subject: [PATCH 19/39] remove some try-catch cruft
---
src/com/fsck/k9/PRNGFixes.java | 9 ++---
src/com/fsck/k9/activity/MessageCompose.java | 9 ++---
src/com/fsck/k9/helper/Utility.java | 12 +++----
src/com/fsck/k9/mail/filter/Base64.java | 10 ++----
.../fsck/k9/mail/internet/DecoderUtil.java | 17 +++-------
src/com/fsck/k9/mail/store/ImapStore.java | 33 +++++--------------
src/com/fsck/k9/view/MessageOpenPgpView.java | 8 ++---
7 files changed, 28 insertions(+), 70 deletions(-)
diff --git a/src/com/fsck/k9/PRNGFixes.java b/src/com/fsck/k9/PRNGFixes.java
index e1b20d23b..33d4ff6c9 100644
--- a/src/com/fsck/k9/PRNGFixes.java
+++ b/src/com/fsck/k9/PRNGFixes.java
@@ -19,7 +19,7 @@ import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.SecureRandom;
@@ -284,10 +284,7 @@ public final class PRNGFixes {
if (serial != null) {
result.append(serial);
}
- try {
- return result.toString().getBytes("UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new RuntimeException("UTF-8 encoding not supported");
- }
+
+ return result.toString().getBytes(Charset.forName("UTF-8"));
}
}
diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java
index 1bda6cb47..05170db2e 100644
--- a/src/com/fsck/k9/activity/MessageCompose.java
+++ b/src/com/fsck/k9/activity/MessageCompose.java
@@ -5,6 +5,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import java.text.DateFormat;
import java.util.ArrayList;
import java.util.Arrays;
@@ -1890,13 +1891,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private InputStream getOpenPgpInputStream() {
String text = buildText(false).getText();
- InputStream is = null;
- try {
- is = new ByteArrayInputStream(text.getBytes("UTF-8"));
- } catch (UnsupportedEncodingException e) {
- Log.e(K9.LOG_TAG, "UnsupportedEncodingException.", e);
- }
- return is;
+ return new ByteArrayInputStream(text.getBytes(Charset.forName("UTF-8")));
}
private void executeOpenPgpMethod(Intent intent) {
diff --git a/src/com/fsck/k9/helper/Utility.java b/src/com/fsck/k9/helper/Utility.java
index a5bea3448..ae8d9bb71 100644
--- a/src/com/fsck/k9/helper/Utility.java
+++ b/src/com/fsck/k9/helper/Utility.java
@@ -20,7 +20,7 @@ import com.fsck.k9.mail.filter.Base64;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
@@ -189,8 +189,8 @@ public class Utility {
* hundreds of times in places that slow down the UI, so it helps.
*/
public static String fastUrlDecode(String s) {
- try {
- byte[] bytes = s.getBytes("UTF-8");
+
+ byte[] bytes = s.getBytes(Charset.forName("UTF-8"));
byte ch;
int length = 0;
for (int i = 0, count = bytes.length; i < count; i++) {
@@ -213,10 +213,8 @@ public class Utility {
}
length++;
}
- return new String(bytes, 0, length, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- return null;
- }
+ return new String(bytes, 0, length, Charset.forName("UTF-8"));
+
}
/*
diff --git a/src/com/fsck/k9/mail/filter/Base64.java b/src/com/fsck/k9/mail/filter/Base64.java
index ffc7abf94..934c3b222 100644
--- a/src/com/fsck/k9/mail/filter/Base64.java
+++ b/src/com/fsck/k9/mail/filter/Base64.java
@@ -17,8 +17,8 @@
package com.fsck.k9.mail.filter;
-import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
+import java.nio.charset.Charset;
/**
* Provides Base64 encoding and decoding as defined by RFC 2045.
@@ -225,12 +225,8 @@ public class Base64 {
}
this.decodeSize = encodeSize - 1;
if (containsBase64Byte(lineSeparator)) {
- String sep;
- try {
- sep = new String(lineSeparator, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- sep = new String(lineSeparator);
- }
+ String sep = new String(lineSeparator, Charset.forName("UTF-8"));
+
throw new IllegalArgumentException("lineSeperator must not contain base64 characters: [" + sep + "]");
}
}
diff --git a/src/com/fsck/k9/mail/internet/DecoderUtil.java b/src/com/fsck/k9/mail/internet/DecoderUtil.java
index 5c7d5034f..80d5b573e 100644
--- a/src/com/fsck/k9/mail/internet/DecoderUtil.java
+++ b/src/com/fsck/k9/mail/internet/DecoderUtil.java
@@ -7,7 +7,8 @@ import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import java.io.ByteArrayInputStream;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+
import org.apache.james.mime4j.codec.Base64InputStream;
import org.apache.james.mime4j.codec.QuotedPrintableInputStream;
import org.apache.james.mime4j.util.CharsetUtil;
@@ -30,12 +31,7 @@ public class DecoderUtil {
* @return the decoded string.
*/
private static String decodeB(String encodedWord, String charset) {
- byte[] bytes;
- try {
- bytes = encodedWord.getBytes("US-ASCII");
- } catch (UnsupportedEncodingException e) {
- return null;
- }
+ byte[] bytes = encodedWord.getBytes(Charset.forName("US-ASCII"));
Base64InputStream is = new Base64InputStream(new ByteArrayInputStream(bytes));
try {
@@ -68,12 +64,7 @@ public class DecoderUtil {
}
}
- byte[] bytes;
- try {
- bytes = sb.toString().getBytes("US-ASCII");
- } catch (UnsupportedEncodingException e) {
- return null;
- }
+ byte[] bytes = sb.toString().getBytes(Charset.forName("US-ASCII"));
QuotedPrintableInputStream is = new QuotedPrintableInputStream(new ByteArrayInputStream(bytes));
try {
diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index 1874d91b6..0f33a473e 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -757,18 +757,10 @@ public class ImapStore extends Store {
}
private String encodeFolderName(String name) {
- try {
- ByteBuffer bb = mModifiedUtf7Charset.encode(name);
- byte[] b = new byte[bb.limit()];
- bb.get(b);
- return new String(b, "US-ASCII");
- } catch (UnsupportedEncodingException uee) {
- /*
- * The only thing that can throw this is getBytes("US-ASCII") and if US-ASCII doesn't
- * exist we're totally screwed.
- */
- throw new RuntimeException("Unable to encode folder name: " + name, uee);
- }
+ ByteBuffer bb = mModifiedUtf7Charset.encode(name);
+ byte[] b = new byte[bb.limit()];
+ bb.get(b);
+ return new String(b, Charset.forName("US-ASCII"));
}
private String decodeFolderName(String name) throws CharacterCodingException {
@@ -776,18 +768,11 @@ public class ImapStore extends Store {
* Convert the encoded name to US-ASCII, then pass it through the modified UTF-7
* decoder and return the Unicode String.
*/
- try {
- // Make sure the decoder throws an exception if it encounters an invalid encoding.
- CharsetDecoder decoder = mModifiedUtf7Charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT);
- CharBuffer cb = decoder.decode(ByteBuffer.wrap(name.getBytes("US-ASCII")));
- return cb.toString();
- } catch (UnsupportedEncodingException uee) {
- /*
- * The only thing that can throw this is getBytes("US-ASCII") and if US-ASCII doesn't
- * exist we're totally screwed.
- */
- throw new RuntimeException("Unable to decode folder name: " + name, uee);
- }
+ // Make sure the decoder throws an exception if it encounters an invalid encoding.
+ CharsetDecoder decoder = mModifiedUtf7Charset.newDecoder().onMalformedInput(CodingErrorAction.REPORT);
+ CharBuffer cb = decoder.decode(ByteBuffer.wrap(name.getBytes(Charset.forName("US-ASCII"))));
+ return cb.toString();
+
}
@Override
diff --git a/src/com/fsck/k9/view/MessageOpenPgpView.java b/src/com/fsck/k9/view/MessageOpenPgpView.java
index dd2cbb40b..1b224112f 100644
--- a/src/com/fsck/k9/view/MessageOpenPgpView.java
+++ b/src/com/fsck/k9/view/MessageOpenPgpView.java
@@ -5,6 +5,7 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
import android.app.Activity;
import android.app.Fragment;
@@ -279,12 +280,7 @@ public class MessageOpenPgpView extends LinearLayout {
String accName = OpenPgpApiHelper.buildAccountName(identity);
intent.putExtra(OpenPgpApi.EXTRA_ACCOUNT_NAME, accName);
- InputStream is = null;
- try {
- is = new ByteArrayInputStream(mData.getBytes("UTF-8"));
- } catch (UnsupportedEncodingException e) {
- Log.e(K9.LOG_TAG, "UnsupportedEncodingException.", e);
- }
+ InputStream is = new ByteArrayInputStream(mData.getBytes(Charset.forName("UTF-8")));
final ByteArrayOutputStream os = new ByteArrayOutputStream();
DecryptVerifyCallback callback = new DecryptVerifyCallback(os, REQUEST_CODE_DECRYPT_VERIFY);
From c438bc1222de27df6bcbd29f8d352dbdb0133246 Mon Sep 17 00:00:00 2001
From: Art O Cathain
Date: Sun, 28 Sep 2014 11:59:11 +0100
Subject: [PATCH 20/39] remove some more catches
---
.../k9/activity/setup/AccountSetupBasics.java | 9 +-
src/com/fsck/k9/helper/UrlEncodingHelper.java | 33 +++++++
src/com/fsck/k9/mail/store/ImapStore.java | 60 +++++--------
src/com/fsck/k9/mail/store/Pop3Store.java | 55 +++++-------
src/com/fsck/k9/mail/store/WebDavStore.java | 88 ++++++-------------
.../fsck/k9/mail/store/local/LocalStore.java | 30 +++----
.../fsck/k9/mail/transport/SmtpTransport.java | 58 +++++-------
src/com/fsck/k9/preferences/Storage.java | 20 ++---
src/com/fsck/k9/view/SingleMessageView.java | 2 +-
9 files changed, 151 insertions(+), 204 deletions(-)
create mode 100644 src/com/fsck/k9/helper/UrlEncodingHelper.java
diff --git a/src/com/fsck/k9/activity/setup/AccountSetupBasics.java b/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
index 861edea53..0e3dcf020 100644
--- a/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
+++ b/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
@@ -3,10 +3,8 @@ package com.fsck.k9.activity.setup;
import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
-import java.net.URLEncoder;
import java.util.Locale;
import android.app.AlertDialog;
@@ -281,8 +279,8 @@ public class AccountSetupBasics extends K9Activity
URI incomingUri = null;
URI outgoingUri = null;
try {
- String userEnc = URLEncoder.encode(user, "UTF-8");
- String passwordEnc = URLEncoder.encode(password, "UTF-8");
+ String userEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(user);
+ String passwordEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(password);
String incomingUsername = mProvider.incomingUsernameTemplate;
incomingUsername = incomingUsername.replaceAll("\\$email", email);
@@ -338,9 +336,6 @@ public class AccountSetupBasics extends K9Activity
}
// Check incoming here. Then check outgoing in onActivityResult()
AccountSetupCheckSettings.actionCheckSettings(this, mAccount, CheckDirection.INCOMING);
- } catch (UnsupportedEncodingException enc) {
- // This really shouldn't happen since the encoding is hardcoded to UTF-8
- Log.e(K9.LOG_TAG, "Couldn't urlencode username or password.", enc);
} catch (URISyntaxException use) {
/*
* If there is some problem with the URI we give up and go on to
diff --git a/src/com/fsck/k9/helper/UrlEncodingHelper.java b/src/com/fsck/k9/helper/UrlEncodingHelper.java
new file mode 100644
index 000000000..2e6f1d591
--- /dev/null
+++ b/src/com/fsck/k9/helper/UrlEncodingHelper.java
@@ -0,0 +1,33 @@
+package com.fsck.k9.helper;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+
+/**
+ * Wraps the java.net.URLDecoder to avoid unhelpful checked exceptions.
+ */
+public class UrlEncodingHelper {
+
+ public static String decodeUtf8(String s) {
+ try {
+ return URLDecoder.decode(s, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ /*
+ * This is impossible, UTF-8 is always supported
+ */
+ throw new RuntimeException("UTF-8 not found");
+ }
+ }
+
+ public static String encodeUtf8(String s) {
+ try {
+ return URLEncoder.encode(s, "UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ /*
+ * This is impossible, UTF-8 is always supported
+ */
+ throw new RuntimeException("UTF-8 not found");
+ }
+ }
+}
diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index 0f33a473e..bc524f6a9 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -7,7 +7,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
@@ -16,8 +15,6 @@ import java.net.SocketAddress;
import java.net.SocketException;
import java.net.URI;
import java.net.URISyntaxException;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
@@ -199,31 +196,26 @@ public class ImapStore extends Store {
}
if (imapUri.getUserInfo() != null) {
- try {
- String userinfo = imapUri.getUserInfo();
- String[] userInfoParts = userinfo.split(":");
+ String userinfo = imapUri.getUserInfo();
+ String[] userInfoParts = userinfo.split(":");
- if (userinfo.endsWith(":")) {
- // Password is empty. This can only happen after an account was imported.
- authenticationType = AuthType.valueOf(userInfoParts[0]);
- username = URLDecoder.decode(userInfoParts[1], "UTF-8");
- } else if (userInfoParts.length == 2) {
- authenticationType = AuthType.PLAIN;
- username = URLDecoder.decode(userInfoParts[0], "UTF-8");
- password = URLDecoder.decode(userInfoParts[1], "UTF-8");
- } else if (userInfoParts.length == 3) {
- authenticationType = AuthType.valueOf(userInfoParts[0]);
- username = URLDecoder.decode(userInfoParts[1], "UTF-8");
+ if (userinfo.endsWith(":")) {
+ // Password is empty. This can only happen after an account was imported.
+ authenticationType = AuthType.valueOf(userInfoParts[0]);
+ username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ } else if (userInfoParts.length == 2) {
+ authenticationType = AuthType.PLAIN;
+ username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ } else if (userInfoParts.length == 3) {
+ authenticationType = AuthType.valueOf(userInfoParts[0]);
+ username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
- if (AuthType.EXTERNAL == authenticationType) {
- clientCertificateAlias = URLDecoder.decode(userInfoParts[2], "UTF-8");
- } else {
- password = URLDecoder.decode(userInfoParts[2], "UTF-8");
- }
+ if (AuthType.EXTERNAL == authenticationType) {
+ clientCertificateAlias = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
+ } else {
+ password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
}
- } catch (UnsupportedEncodingException enc) {
- // This shouldn't happen since the encoding is hardcoded to UTF-8
- throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
}
}
@@ -261,19 +253,11 @@ public class ImapStore extends Store {
* @see ImapStore#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
- String userEnc;
- String passwordEnc;
- String clientCertificateAliasEnc;
- try {
- userEnc = URLEncoder.encode(server.username, "UTF-8");
- passwordEnc = (server.password != null) ?
- URLEncoder.encode(server.password, "UTF-8") : "";
- clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
- URLEncoder.encode(server.clientCertificateAlias, "UTF-8") : "";
- }
- catch (UnsupportedEncodingException e) {
- throw new IllegalArgumentException("Could not encode username or password", e);
- }
+ String userEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.username);
+ String passwordEnc = (server.password != null) ?
+ com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.password) : "";
+ String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
+ com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java
index 2fc4fc5f2..6b3cf48fa 100644
--- a/src/com/fsck/k9/mail/store/Pop3Store.java
+++ b/src/com/fsck/k9/mail/store/Pop3Store.java
@@ -115,28 +115,23 @@ public class Pop3Store extends Store {
AuthType authType = AuthType.PLAIN;
if (pop3Uri.getUserInfo() != null) {
- try {
- int userIndex = 0, passwordIndex = 1;
- String userinfo = pop3Uri.getUserInfo();
- String[] userInfoParts = userinfo.split(":");
- if (userInfoParts.length > 2 || userinfo.endsWith(":") ) {
- // If 'userinfo' ends with ":" the password is empty. This can only happen
- // after an account was imported (so authType and username are present).
- userIndex++;
- passwordIndex++;
- authType = AuthType.valueOf(userInfoParts[0]);
+ int userIndex = 0, passwordIndex = 1;
+ String userinfo = pop3Uri.getUserInfo();
+ String[] userInfoParts = userinfo.split(":");
+ if (userInfoParts.length > 2 || userinfo.endsWith(":") ) {
+ // If 'userinfo' ends with ":" the password is empty. This can only happen
+ // after an account was imported (so authType and username are present).
+ userIndex++;
+ passwordIndex++;
+ authType = AuthType.valueOf(userInfoParts[0]);
+ }
+ username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[userIndex]);
+ if (userInfoParts.length > passwordIndex) {
+ if (authType == AuthType.EXTERNAL) {
+ clientCertificateAlias = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
+ } else {
+ password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
}
- username = URLDecoder.decode(userInfoParts[userIndex], "UTF-8");
- if (userInfoParts.length > passwordIndex) {
- if (authType == AuthType.EXTERNAL) {
- clientCertificateAlias = URLDecoder.decode(userInfoParts[passwordIndex], "UTF-8");
- } else {
- password = URLDecoder.decode(userInfoParts[passwordIndex], "UTF-8");
- }
- }
- } catch (UnsupportedEncodingException enc) {
- // This shouldn't happen since the encoding is hardcoded to UTF-8
- throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
}
}
@@ -156,19 +151,11 @@ public class Pop3Store extends Store {
* @see Pop3Store#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
- String userEnc;
- String passwordEnc;
- String clientCertificateAliasEnc;
- try {
- userEnc = URLEncoder.encode(server.username, "UTF-8");
- passwordEnc = (server.password != null) ?
- URLEncoder.encode(server.password, "UTF-8") : "";
- clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
- URLEncoder.encode(server.clientCertificateAlias, "UTF-8") : "";
- }
- catch (UnsupportedEncodingException e) {
- throw new IllegalArgumentException("Could not encode username or password", e);
- }
+ String userEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.username);
+ String passwordEnc = (server.password != null) ?
+ com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.password) : "";
+ String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
+ com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java
index 6e21aef9f..5faca357f 100644
--- a/src/com/fsck/k9/mail/store/WebDavStore.java
+++ b/src/com/fsck/k9/mail/store/WebDavStore.java
@@ -5,6 +5,7 @@ import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.controller.MessageRetrievalListener;
+import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.filter.EOLConvertingOutputStream;
@@ -139,22 +140,17 @@ public class WebDavStore extends Store {
String userInfo = webDavUri.getUserInfo();
if (userInfo != null) {
- try {
- String[] userInfoParts = userInfo.split(":");
- username = URLDecoder.decode(userInfoParts[0], "UTF-8");
- String userParts[] = username.split("\\\\", 2);
+ String[] userInfoParts = userInfo.split(":");
+ username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ String userParts[] = username.split("\\\\", 2);
- if (userParts.length > 1) {
- alias = userParts[1];
- } else {
- alias = username;
- }
- if (userInfoParts.length > 1) {
- password = URLDecoder.decode(userInfoParts[1], "UTF-8");
- }
- } catch (UnsupportedEncodingException enc) {
- // This shouldn't happen since the encoding is hardcoded to UTF-8
- throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
+ if (userParts.length > 1) {
+ alias = userParts[1];
+ } else {
+ alias = username;
+ }
+ if (userInfoParts.length > 1) {
+ password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
}
}
@@ -194,16 +190,9 @@ public class WebDavStore extends Store {
* @see WebDavStore#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
- String userEnc;
- String passwordEnc;
- try {
- userEnc = URLEncoder.encode(server.username, "UTF-8");
- passwordEnc = (server.password != null) ?
- URLEncoder.encode(server.password, "UTF-8") : "";
- }
- catch (UnsupportedEncodingException e) {
- throw new IllegalArgumentException("Could not encode username or password", e);
- }
+ String userEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.username);
+ String passwordEnc = (server.password != null) ?
+ com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.password) : "";
String scheme;
switch (server.connectionSecurity) {
@@ -479,7 +468,6 @@ public class WebDavStore extends Store {
}
if (folderSlash > 0) {
- String folderName;
String fullPathName;
// Removes the final slash if present
@@ -489,17 +477,8 @@ public class WebDavStore extends Store {
fullPathName = folderUrl.substring(folderSlash + 1);
// Decodes the url-encoded folder name (i.e. "My%20folder" => "My Folder"
- try {
- folderName = java.net.URLDecoder.decode(fullPathName, "UTF-8");
- } catch (UnsupportedEncodingException uee) {
- /**
- * If we don't support UTF-8 there's a problem, don't decode
- * it then
- */
- folderName = fullPathName;
- }
- return folderName;
+ return UrlEncodingHelper.decodeUtf8(fullPathName);
}
return null;
@@ -1258,21 +1237,16 @@ public class WebDavStore extends Store {
this.mName = name;
String encodedName = "";
- try {
- String[] urlParts = name.split("/");
- String url = "";
- for (int i = 0, count = urlParts.length; i < count; i++) {
- if (i != 0) {
- url = url + "/" + java.net.URLEncoder.encode(urlParts[i], "UTF-8");
- } else {
- url = java.net.URLEncoder.encode(urlParts[i], "UTF-8");
- }
+ String[] urlParts = name.split("/");
+ String url = "";
+ for (int i = 0, count = urlParts.length; i < count; i++) {
+ if (i != 0) {
+ url = url + "/" + com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(urlParts[i]);
+ } else {
+ url = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(urlParts[i]);
}
- encodedName = url;
- } catch (UnsupportedEncodingException uee) {
- Log.e(K9.LOG_TAG, "UnsupportedEncodingException URLEncoding folder name, skipping encoded");
- encodedName = name;
}
+ encodedName = url;
encodedName = encodedName.replaceAll("\\+", "%20");
@@ -1910,7 +1884,7 @@ public class WebDavStore extends Store {
if (!messageURL.endsWith("/")) {
messageURL += "/";
}
- messageURL += URLEncoder.encode(message.getUid() + ":" + System.currentTimeMillis() + ".eml", "UTF-8");
+ messageURL += com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(message.getUid() + ":" + System.currentTimeMillis() + ".eml");
Log.i(K9.LOG_TAG, "Uploading message as " + messageURL);
@@ -1997,12 +1971,9 @@ public class WebDavStore extends Store {
* We have to decode, then encode the URL because Exchange likes to not properly encode all characters
*/
try {
- end = java.net.URLDecoder.decode(end, "UTF-8");
- end = java.net.URLEncoder.encode(end, "UTF-8");
+ end = UrlEncodingHelper.decodeUtf8(end);
+ end = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(end);
end = end.replaceAll("\\+", "%20");
- } catch (UnsupportedEncodingException uee) {
- Log.e(K9.LOG_TAG, "UnsupportedEncodingException caught in setUrl: " + uee + "\nTrace: "
- + processException(uee));
} catch (IllegalArgumentException iae) {
Log.e(K9.LOG_TAG, "IllegalArgumentException caught in setUrl: " + iae + "\nTrace: "
+ processException(iae));
@@ -2405,13 +2376,10 @@ public class WebDavStore extends Store {
*/
try {
if (length > 3) {
- end = java.net.URLDecoder.decode(end, "UTF-8");
- end = java.net.URLEncoder.encode(end, "UTF-8");
+ end = UrlEncodingHelper.decodeUtf8(end);
+ end = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(end);
end = end.replaceAll("\\+", "%20");
}
- } catch (UnsupportedEncodingException uee) {
- Log.e(K9.LOG_TAG, "UnsupportedEncodingException caught in HttpGeneric(String uri): " + uee
- + "\nTrace: " + processException(uee));
} catch (IllegalArgumentException iae) {
Log.e(K9.LOG_TAG, "IllegalArgumentException caught in HttpGeneric(String uri): " + iae + "\nTrace: "
+ processException(iae));
diff --git a/src/com/fsck/k9/mail/store/local/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
index 3442aa988..b6d7dff8e 100644
--- a/src/com/fsck/k9/mail/store/local/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -3,8 +3,6 @@ package com.fsck.k9.mail.store.local;
import java.io.File;
import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
@@ -468,23 +466,19 @@ public class LocalStore extends Store implements Serializable {
}
public void addPendingCommand(PendingCommand command) throws UnavailableStorageException {
- try {
- for (int i = 0; i < command.arguments.length; i++) {
- command.arguments[i] = URLEncoder.encode(command.arguments[i], "UTF-8");
- }
- final ContentValues cv = new ContentValues();
- cv.put("command", command.command);
- cv.put("arguments", Utility.combine(command.arguments, ','));
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- db.insert("pending_commands", "command", cv);
- return null;
- }
- });
- } catch (UnsupportedEncodingException uee) {
- throw new Error("Aparently UTF-8 has been lost to the annals of history.");
+ for (int i = 0; i < command.arguments.length; i++) {
+ command.arguments[i] = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(command.arguments[i]);
}
+ final ContentValues cv = new ContentValues();
+ cv.put("command", command.command);
+ cv.put("arguments", Utility.combine(command.arguments, ','));
+ database.execute(false, new DbCallback() {
+ @Override
+ public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
+ db.insert("pending_commands", "command", cv);
+ return null;
+ }
+ });
}
public void removePendingCommand(final PendingCommand command) throws UnavailableStorageException {
diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java
index 8c6d178b4..f90d1999b 100644
--- a/src/com/fsck/k9/mail/transport/SmtpTransport.java
+++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java
@@ -23,7 +23,6 @@ import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-import java.io.UnsupportedEncodingException;
import java.net.*;
import java.security.GeneralSecurityException;
import java.util.*;
@@ -92,28 +91,23 @@ public class SmtpTransport extends Transport {
}
if (smtpUri.getUserInfo() != null) {
- try {
- String[] userInfoParts = smtpUri.getUserInfo().split(":");
- if (userInfoParts.length == 1) {
- authType = AuthType.PLAIN;
- username = URLDecoder.decode(userInfoParts[0], "UTF-8");
- } else if (userInfoParts.length == 2) {
- authType = AuthType.PLAIN;
- username = URLDecoder.decode(userInfoParts[0], "UTF-8");
- password = URLDecoder.decode(userInfoParts[1], "UTF-8");
- } else if (userInfoParts.length == 3) {
- // NOTE: In SmptTransport URIs, the authType comes last!
- authType = AuthType.valueOf(userInfoParts[2]);
- username = URLDecoder.decode(userInfoParts[0], "UTF-8");
- if (authType == AuthType.EXTERNAL) {
- clientCertificateAlias = URLDecoder.decode(userInfoParts[1], "UTF-8");
- } else {
- password = URLDecoder.decode(userInfoParts[1], "UTF-8");
- }
+ String[] userInfoParts = smtpUri.getUserInfo().split(":");
+ if (userInfoParts.length == 1) {
+ authType = AuthType.PLAIN;
+ username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ } else if (userInfoParts.length == 2) {
+ authType = AuthType.PLAIN;
+ username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ } else if (userInfoParts.length == 3) {
+ // NOTE: In SmptTransport URIs, the authType comes last!
+ authType = AuthType.valueOf(userInfoParts[2]);
+ username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ if (authType == AuthType.EXTERNAL) {
+ clientCertificateAlias = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ } else {
+ password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
}
- } catch (UnsupportedEncodingException enc) {
- // This shouldn't happen since the encoding is hardcoded to UTF-8
- throw new IllegalArgumentException("Couldn't urldecode username or password.", enc);
}
}
@@ -133,20 +127,12 @@ public class SmtpTransport extends Transport {
* @see SmtpTransport#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
- String userEnc;
- String passwordEnc;
- String clientCertificateAliasEnc;
- try {
- userEnc = (server.username != null) ?
- URLEncoder.encode(server.username, "UTF-8") : "";
- passwordEnc = (server.password != null) ?
- URLEncoder.encode(server.password, "UTF-8") : "";
- clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
- URLEncoder.encode(server.clientCertificateAlias, "UTF-8") : "";
- }
- catch (UnsupportedEncodingException e) {
- throw new IllegalArgumentException("Could not encode username or password", e);
- }
+ String userEnc = (server.username != null) ?
+ com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.username) : "";
+ String passwordEnc = (server.password != null) ?
+ com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.password) : "";
+ String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
+ com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
diff --git a/src/com/fsck/k9/preferences/Storage.java b/src/com/fsck/k9/preferences/Storage.java
index e4a624847..84f542d5a 100644
--- a/src/com/fsck/k9/preferences/Storage.java
+++ b/src/com/fsck/k9/preferences/Storage.java
@@ -59,11 +59,11 @@ public class Storage implements SharedPreferences {
if (transportUriStr != null) {
String[] userInfoParts = uri.getUserInfo().split(":");
- String usernameEnc = URLEncoder.encode(userInfoParts[0], "UTF-8");
+ String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = "";
String authType = "";
if (userInfoParts.length > 1) {
- passwordEnc = ":" + URLEncoder.encode(userInfoParts[1], "UTF-8");
+ passwordEnc = ":" + com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
}
if (userInfoParts.length > 2) {
authType = ":" + userInfoParts[2];
@@ -83,34 +83,34 @@ public class Storage implements SharedPreferences {
if (storeUriStr.startsWith("imap")) {
String[] userInfoParts = uri.getUserInfo().split(":");
if (userInfoParts.length == 2) {
- String usernameEnc = URLEncoder.encode(userInfoParts[0], "UTF-8");
- String passwordEnc = URLEncoder.encode(userInfoParts[1], "UTF-8");
+ String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
+ String passwordEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
newUserInfo = usernameEnc + ":" + passwordEnc;
} else {
String authType = userInfoParts[0];
- String usernameEnc = URLEncoder.encode(userInfoParts[1], "UTF-8");
- String passwordEnc = URLEncoder.encode(userInfoParts[2], "UTF-8");
+ String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
+ String passwordEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[2]);
newUserInfo = authType + ":" + usernameEnc + ":" + passwordEnc;
}
} else if (storeUriStr.startsWith("pop3")) {
String[] userInfoParts = uri.getUserInfo().split(":", 2);
- String usernameEnc = URLEncoder.encode(userInfoParts[0], "UTF-8");
+ String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = "";
if (userInfoParts.length > 1) {
- passwordEnc = ":" + URLEncoder.encode(userInfoParts[1], "UTF-8");
+ passwordEnc = ":" + com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
}
newUserInfo = usernameEnc + passwordEnc;
} else if (storeUriStr.startsWith("webdav")) {
String[] userInfoParts = uri.getUserInfo().split(":", 2);
- String usernameEnc = URLEncoder.encode(userInfoParts[0], "UTF-8");
+ String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = "";
if (userInfoParts.length > 1) {
- passwordEnc = ":" + URLEncoder.encode(userInfoParts[1], "UTF-8");
+ passwordEnc = ":" + com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
}
newUserInfo = usernameEnc + passwordEnc;
diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java
index f9e1cdda1..a7407ca4f 100644
--- a/src/com/fsck/k9/view/SingleMessageView.java
+++ b/src/com/fsck/k9/view/SingleMessageView.java
@@ -790,7 +790,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
// Try to get the filename from the URL
int start = path.lastIndexOf("/");
if (start != -1 && start + 1 < path.length()) {
- filename = URLDecoder.decode(path.substring(start + 1), "UTF-8");
+ filename = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(path.substring(start + 1));
} else {
// Use a dummy filename if necessary
filename = "saved_image";
From 2226ae6a8e1455d2fb3bdc3e7c6c771ab39616df Mon Sep 17 00:00:00 2001
From: Art O Cathain
Date: Sun, 28 Sep 2014 12:48:46 +0100
Subject: [PATCH 21/39] fix IDE error
---
src/com/fsck/k9/mail/store/local/LocalStore.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/com/fsck/k9/mail/store/local/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
index b6d7dff8e..0591fe253 100644
--- a/src/com/fsck/k9/mail/store/local/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -100,7 +100,7 @@ public class LocalStore extends Store implements Serializable {
/**
* Maximum number of messages to perform flag updates on at once.
*
- * @see #setFlag(List, Flag, boolean, boolean)
+ * @see #setFlag(List, Flag, boolean)
*/
private static final int FLAG_UPDATE_BATCH_SIZE = 500;
From 010d8c9f7e03a39731c85fbb85fd8524a65582d8 Mon Sep 17 00:00:00 2001
From: Art O Cathain
Date: Mon, 29 Sep 2014 18:06:21 +0100
Subject: [PATCH 22/39] always use import for UrlEncodingHelper
---
.../k9/activity/setup/AccountSetupBasics.java | 5 +++--
src/com/fsck/k9/mail/store/ImapStore.java | 19 +++++++++--------
src/com/fsck/k9/mail/store/Pop3Store.java | 13 ++++++------
src/com/fsck/k9/mail/store/WebDavStore.java | 21 +++++++++----------
.../fsck/k9/mail/store/local/LocalStore.java | 3 ++-
.../fsck/k9/mail/transport/SmtpTransport.java | 19 +++++++++--------
src/com/fsck/k9/preferences/Storage.java | 21 ++++++++++---------
src/com/fsck/k9/view/SingleMessageView.java | 3 ++-
8 files changed, 55 insertions(+), 49 deletions(-)
diff --git a/src/com/fsck/k9/activity/setup/AccountSetupBasics.java b/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
index 0e3dcf020..56d409743 100644
--- a/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
+++ b/src/com/fsck/k9/activity/setup/AccountSetupBasics.java
@@ -33,6 +33,7 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.K9Activity;
import com.fsck.k9.activity.setup.AccountSetupCheckSettings.CheckDirection;
+import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ConnectionSecurity;
@@ -279,8 +280,8 @@ public class AccountSetupBasics extends K9Activity
URI incomingUri = null;
URI outgoingUri = null;
try {
- String userEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(user);
- String passwordEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(password);
+ String userEnc = UrlEncodingHelper.encodeUtf8(user);
+ String passwordEnc = UrlEncodingHelper.encodeUtf8(password);
String incomingUsername = mProvider.incomingUsernameTemplate;
incomingUsername = incomingUsername.replaceAll("\\$email", email);
diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index bc524f6a9..13bf350af 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -62,6 +62,7 @@ import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.StringUtils;
+import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.helper.power.TracingPowerManager;
import com.fsck.k9.helper.power.TracingPowerManager.TracingWakeLock;
@@ -202,19 +203,19 @@ public class ImapStore extends Store {
if (userinfo.endsWith(":")) {
// Password is empty. This can only happen after an account was imported.
authenticationType = AuthType.valueOf(userInfoParts[0]);
- username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} else if (userInfoParts.length == 2) {
authenticationType = AuthType.PLAIN;
- username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
- password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} else if (userInfoParts.length == 3) {
authenticationType = AuthType.valueOf(userInfoParts[0]);
- username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
if (AuthType.EXTERNAL == authenticationType) {
- clientCertificateAlias = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
+ clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
} else {
- password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
+ password = UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
}
}
}
@@ -253,11 +254,11 @@ public class ImapStore extends Store {
* @see ImapStore#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
- String userEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.username);
+ String userEnc = UrlEncodingHelper.encodeUtf8(server.username);
String passwordEnc = (server.password != null) ?
- com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.password) : "";
+ UrlEncodingHelper.encodeUtf8(server.password) : "";
String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
- com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
+ UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java
index 6b3cf48fa..10d6f8231 100644
--- a/src/com/fsck/k9/mail/store/Pop3Store.java
+++ b/src/com/fsck/k9/mail/store/Pop3Store.java
@@ -7,6 +7,7 @@ import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
import com.fsck.k9.controller.MessageRetrievalListener;
+import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.filter.Base64;
@@ -125,12 +126,12 @@ public class Pop3Store extends Store {
passwordIndex++;
authType = AuthType.valueOf(userInfoParts[0]);
}
- username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[userIndex]);
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[userIndex]);
if (userInfoParts.length > passwordIndex) {
if (authType == AuthType.EXTERNAL) {
- clientCertificateAlias = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
+ clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
} else {
- password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
+ password = UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
}
}
}
@@ -151,11 +152,11 @@ public class Pop3Store extends Store {
* @see Pop3Store#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
- String userEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.username);
+ String userEnc = UrlEncodingHelper.encodeUtf8(server.username);
String passwordEnc = (server.password != null) ?
- com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.password) : "";
+ UrlEncodingHelper.encodeUtf8(server.password) : "";
String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
- com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
+ UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java
index 5faca357f..1148927bb 100644
--- a/src/com/fsck/k9/mail/store/WebDavStore.java
+++ b/src/com/fsck/k9/mail/store/WebDavStore.java
@@ -5,6 +5,7 @@ import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.controller.MessageRetrievalListener;
+
import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
@@ -41,8 +42,6 @@ import javax.xml.parsers.SAXParserFactory;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
-import java.net.URLDecoder;
-import java.net.URLEncoder;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.text.DateFormat;
@@ -141,7 +140,7 @@ public class WebDavStore extends Store {
String userInfo = webDavUri.getUserInfo();
if (userInfo != null) {
String[] userInfoParts = userInfo.split(":");
- username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
String userParts[] = username.split("\\\\", 2);
if (userParts.length > 1) {
@@ -150,7 +149,7 @@ public class WebDavStore extends Store {
alias = username;
}
if (userInfoParts.length > 1) {
- password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
}
}
@@ -190,9 +189,9 @@ public class WebDavStore extends Store {
* @see WebDavStore#decodeUri(String)
*/
public static String createUri(ServerSettings server) {
- String userEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.username);
+ String userEnc = UrlEncodingHelper.encodeUtf8(server.username);
String passwordEnc = (server.password != null) ?
- com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.password) : "";
+ UrlEncodingHelper.encodeUtf8(server.password) : "";
String scheme;
switch (server.connectionSecurity) {
@@ -1241,9 +1240,9 @@ public class WebDavStore extends Store {
String url = "";
for (int i = 0, count = urlParts.length; i < count; i++) {
if (i != 0) {
- url = url + "/" + com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(urlParts[i]);
+ url = url + "/" + UrlEncodingHelper.encodeUtf8(urlParts[i]);
} else {
- url = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(urlParts[i]);
+ url = UrlEncodingHelper.encodeUtf8(urlParts[i]);
}
}
encodedName = url;
@@ -1884,7 +1883,7 @@ public class WebDavStore extends Store {
if (!messageURL.endsWith("/")) {
messageURL += "/";
}
- messageURL += com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(message.getUid() + ":" + System.currentTimeMillis() + ".eml");
+ messageURL += UrlEncodingHelper.encodeUtf8(message.getUid() + ":" + System.currentTimeMillis() + ".eml");
Log.i(K9.LOG_TAG, "Uploading message as " + messageURL);
@@ -1972,7 +1971,7 @@ public class WebDavStore extends Store {
*/
try {
end = UrlEncodingHelper.decodeUtf8(end);
- end = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(end);
+ end = UrlEncodingHelper.encodeUtf8(end);
end = end.replaceAll("\\+", "%20");
} catch (IllegalArgumentException iae) {
Log.e(K9.LOG_TAG, "IllegalArgumentException caught in setUrl: " + iae + "\nTrace: "
@@ -2377,7 +2376,7 @@ public class WebDavStore extends Store {
try {
if (length > 3) {
end = UrlEncodingHelper.decodeUtf8(end);
- end = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(end);
+ end = UrlEncodingHelper.encodeUtf8(end);
end = end.replaceAll("\\+", "%20");
}
} catch (IllegalArgumentException iae) {
diff --git a/src/com/fsck/k9/mail/store/local/LocalStore.java b/src/com/fsck/k9/mail/store/local/LocalStore.java
index 0591fe253..e200ae087 100644
--- a/src/com/fsck/k9/mail/store/local/LocalStore.java
+++ b/src/com/fsck/k9/mail/store/local/LocalStore.java
@@ -26,6 +26,7 @@ import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.helper.StringUtils;
+import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
@@ -467,7 +468,7 @@ public class LocalStore extends Store implements Serializable {
public void addPendingCommand(PendingCommand command) throws UnavailableStorageException {
for (int i = 0; i < command.arguments.length; i++) {
- command.arguments[i] = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(command.arguments[i]);
+ command.arguments[i] = UrlEncodingHelper.encodeUtf8(command.arguments[i]);
}
final ContentValues cv = new ContentValues();
cv.put("command", command.command);
diff --git a/src/com/fsck/k9/mail/transport/SmtpTransport.java b/src/com/fsck/k9/mail/transport/SmtpTransport.java
index f90d1999b..5d68104af 100644
--- a/src/com/fsck/k9/mail/transport/SmtpTransport.java
+++ b/src/com/fsck/k9/mail/transport/SmtpTransport.java
@@ -6,6 +6,7 @@ import android.util.Log;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.R;
+import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.*;
import com.fsck.k9.mail.Message.RecipientType;
@@ -94,19 +95,19 @@ public class SmtpTransport extends Transport {
String[] userInfoParts = smtpUri.getUserInfo().split(":");
if (userInfoParts.length == 1) {
authType = AuthType.PLAIN;
- username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
} else if (userInfoParts.length == 2) {
authType = AuthType.PLAIN;
- username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
- password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} else if (userInfoParts.length == 3) {
// NOTE: In SmptTransport URIs, the authType comes last!
authType = AuthType.valueOf(userInfoParts[2]);
- username = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
if (authType == AuthType.EXTERNAL) {
- clientCertificateAlias = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
} else {
- password = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
}
}
}
@@ -128,11 +129,11 @@ public class SmtpTransport extends Transport {
*/
public static String createUri(ServerSettings server) {
String userEnc = (server.username != null) ?
- com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.username) : "";
+ UrlEncodingHelper.encodeUtf8(server.username) : "";
String passwordEnc = (server.password != null) ?
- com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.password) : "";
+ UrlEncodingHelper.encodeUtf8(server.password) : "";
String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
- com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
+ UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
diff --git a/src/com/fsck/k9/preferences/Storage.java b/src/com/fsck/k9/preferences/Storage.java
index 84f542d5a..e88a9b6c3 100644
--- a/src/com/fsck/k9/preferences/Storage.java
+++ b/src/com/fsck/k9/preferences/Storage.java
@@ -9,6 +9,7 @@ import android.database.sqlite.SQLiteStatement;
import android.util.Log;
import com.fsck.k9.K9;
+import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import java.net.URI;
@@ -59,11 +60,11 @@ public class Storage implements SharedPreferences {
if (transportUriStr != null) {
String[] userInfoParts = uri.getUserInfo().split(":");
- String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
+ String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = "";
String authType = "";
if (userInfoParts.length > 1) {
- passwordEnc = ":" + com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
+ passwordEnc = ":" + UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
}
if (userInfoParts.length > 2) {
authType = ":" + userInfoParts[2];
@@ -83,34 +84,34 @@ public class Storage implements SharedPreferences {
if (storeUriStr.startsWith("imap")) {
String[] userInfoParts = uri.getUserInfo().split(":");
if (userInfoParts.length == 2) {
- String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
- String passwordEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
+ String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
+ String passwordEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
newUserInfo = usernameEnc + ":" + passwordEnc;
} else {
String authType = userInfoParts[0];
- String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
- String passwordEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[2]);
+ String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
+ String passwordEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[2]);
newUserInfo = authType + ":" + usernameEnc + ":" + passwordEnc;
}
} else if (storeUriStr.startsWith("pop3")) {
String[] userInfoParts = uri.getUserInfo().split(":", 2);
- String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
+ String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = "";
if (userInfoParts.length > 1) {
- passwordEnc = ":" + com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
+ passwordEnc = ":" + UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
}
newUserInfo = usernameEnc + passwordEnc;
} else if (storeUriStr.startsWith("webdav")) {
String[] userInfoParts = uri.getUserInfo().split(":", 2);
- String usernameEnc = com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
+ String usernameEnc = UrlEncodingHelper.encodeUtf8(userInfoParts[0]);
String passwordEnc = "";
if (userInfoParts.length > 1) {
- passwordEnc = ":" + com.fsck.k9.helper.UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
+ passwordEnc = ":" + UrlEncodingHelper.encodeUtf8(userInfoParts[1]);
}
newUserInfo = usernameEnc + passwordEnc;
diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java
index a7407ca4f..bca3eef0d 100644
--- a/src/com/fsck/k9/view/SingleMessageView.java
+++ b/src/com/fsck/k9/view/SingleMessageView.java
@@ -47,6 +47,7 @@ import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.helper.ClipboardManager;
import com.fsck.k9.helper.Contacts;
import com.fsck.k9.helper.HtmlConverter;
+import com.fsck.k9.helper.UrlEncodingHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
@@ -790,7 +791,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
// Try to get the filename from the URL
int start = path.lastIndexOf("/");
if (start != -1 && start + 1 < path.length()) {
- filename = com.fsck.k9.helper.UrlEncodingHelper.decodeUtf8(path.substring(start + 1));
+ filename = UrlEncodingHelper.decodeUtf8(path.substring(start + 1));
} else {
// Use a dummy filename if necessary
filename = "saved_image";
From 203dcfe2c3d63f0e6ff79c2c5566115c2571a35c Mon Sep 17 00:00:00 2001
From: Art O Cathain
Date: Sat, 4 Oct 2014 11:45:45 +0100
Subject: [PATCH 23/39] use interfaces, not implementions
---
src/com/fsck/k9/Account.java | 2 +-
src/com/fsck/k9/K9.java | 3 +-
.../AccessibleEmailContentActivity.java | 6 +-
src/com/fsck/k9/activity/Accounts.java | 8 +-
src/com/fsck/k9/activity/FolderList.java | 6 +-
.../fsck/k9/activity/FolderListFilter.java | 8 +-
src/com/fsck/k9/activity/MessageCompose.java | 10 +-
.../NotificationDeleteConfirmation.java | 8 +-
.../k9/controller/MessagingController.java | 36 +++----
.../fsck/k9/fragment/MessageListFragment.java | 4 +-
src/com/fsck/k9/helper/Contacts.java | 3 +-
src/com/fsck/k9/helper/Utility.java | 22 +++++
src/com/fsck/k9/mail/Address.java | 2 +-
src/com/fsck/k9/mail/Multipart.java | 2 +-
src/com/fsck/k9/mail/Store.java | 8 +-
src/com/fsck/k9/mail/internet/MimeHeader.java | 6 +-
src/com/fsck/k9/mail/store/ImapStore.java | 20 ++--
src/com/fsck/k9/mail/store/Pop3Store.java | 16 ++--
src/com/fsck/k9/mail/store/WebDavStore.java | 94 +++++++++----------
.../fsck/k9/mail/store/imap/ImapUtility.java | 4 +-
.../fsck/k9/mail/store/local/LocalFolder.java | 2 +-
.../k9/mail/store/local/LocalMessage.java | 3 +-
.../fsck/k9/mail/store/local/LocalStore.java | 10 +-
.../fsck/k9/mail/transport/SmtpTransport.java | 22 ++---
src/com/fsck/k9/preferences/Editor.java | 5 +-
src/com/fsck/k9/preferences/Storage.java | 24 +++--
.../fsck/k9/search/ConditionsTreeNode.java | 2 +-
src/com/fsck/k9/search/LocalSearch.java | 2 +-
.../k9/service/NotificationActionService.java | 20 ++--
src/com/fsck/k9/service/PollService.java | 3 +-
.../fsck/k9/view/NonLockingScrollView.java | 5 +-
31 files changed, 205 insertions(+), 161 deletions(-)
diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java
index 323d53762..faf9d1e7c 100644
--- a/src/com/fsck/k9/Account.java
+++ b/src/com/fsck/k9/Account.java
@@ -181,7 +181,7 @@ public class Account implements BaseAccount {
private boolean mPushPollOnConnect;
private boolean mNotifySync;
private SortType mSortType;
- private HashMap mSortAscending = new HashMap();
+ private Map mSortAscending = new HashMap();
private ShowPictures mShowPictures;
private boolean mIsSignatureBeforeQuotedText;
private String mExpungePolicy = EXPUNGE_IMMEDIATELY;
diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java
index 9eead455a..6b5d83d56 100644
--- a/src/com/fsck/k9/K9.java
+++ b/src/com/fsck/k9/K9.java
@@ -5,6 +5,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
@@ -263,7 +264,7 @@ public class K9 extends Application {
private static boolean mHideTimeZone = false;
private static SortType mSortType;
- private static HashMap mSortAscending = new HashMap();
+ private static Map mSortAscending = new HashMap();
private static boolean sUseBackgroundAsUnreadIndicator = true;
private static boolean sThreadedViewEnabled = true;
diff --git a/src/com/fsck/k9/activity/AccessibleEmailContentActivity.java b/src/com/fsck/k9/activity/AccessibleEmailContentActivity.java
index 4b62641e4..b81c0cb09 100644
--- a/src/com/fsck/k9/activity/AccessibleEmailContentActivity.java
+++ b/src/com/fsck/k9/activity/AccessibleEmailContentActivity.java
@@ -17,6 +17,8 @@
package com.fsck.k9.activity;
import java.util.ArrayList;
+import java.util.List;
+
import android.app.ListActivity;
import android.os.Bundle;
import android.text.Html;
@@ -40,7 +42,7 @@ public class AccessibleEmailContentActivity extends ListActivity {
Spanned parsedHtml = Html.fromHtml(htmlSource, null, null);
String[] rawListItems = parsedHtml.toString().split("\n");
- ArrayList cleanedList = new ArrayList();
+ List cleanedList = new ArrayList();
for (String rawListItem : rawListItems) {
if (rawListItem.trim().length() > 0) {
addToCleanedList(cleanedList, rawListItem);
@@ -53,7 +55,7 @@ public class AccessibleEmailContentActivity extends ListActivity {
setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, listItems));
}
- private void addToCleanedList(ArrayList cleanedList, String line) {
+ private void addToCleanedList(List cleanedList, String line) {
if (line.length() < 80) {
cleanedList.add(line);
} else {
diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index 76cfe3abb..0b9a76f06 100644
--- a/src/com/fsck/k9/activity/Accounts.java
+++ b/src/com/fsck/k9/activity/Accounts.java
@@ -13,6 +13,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import android.app.ActionBar;
import android.app.Activity;
@@ -75,6 +76,7 @@ import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.activity.setup.WelcomeMessage;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.SizeFormatter;
+import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.AuthType;
import com.fsck.k9.mail.ServerSettings;
import com.fsck.k9.mail.Store;
@@ -120,9 +122,9 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
private static final int DIALOG_RECREATE_ACCOUNT = 3;
private static final int DIALOG_NO_FILE_MANAGER = 4;
- private ConcurrentHashMap accountStats = new ConcurrentHashMap();
+ private ConcurrentMap accountStats = new ConcurrentHashMap();
- private ConcurrentHashMap pendingWork = new ConcurrentHashMap();
+ private ConcurrentMap pendingWork = new ConcurrentHashMap();
private BaseAccount mSelectedContextAccount;
private int mUnreadMessageCount = 0;
@@ -495,7 +497,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
outState.putString(SELECTED_CONTEXT_ACCOUNT, mSelectedContextAccount.getUuid());
}
outState.putSerializable(STATE_UNREAD_COUNT, mUnreadMessageCount);
- outState.putSerializable(ACCOUNT_STATS, accountStats);
+ outState.putSerializable(ACCOUNT_STATS, Utility.toSerializableConcurrentMap(accountStats));
}
private StorageManager.StorageListener storageListener = new StorageManager.StorageListener() {
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index bd5be538b..217658b33 100644
--- a/src/com/fsck/k9/activity/FolderList.java
+++ b/src/com/fsck/k9/activity/FolderList.java
@@ -660,7 +660,7 @@ public class FolderList extends K9ListActivity {
}
class FolderListAdapter extends BaseAdapter implements Filterable {
- private ArrayList mFolders = new ArrayList();
+ private List mFolders = new ArrayList();
private List mFilteredFolders = Collections.unmodifiableList(mFolders);
private Filter mFilter = new FolderListFilter();
@@ -1176,7 +1176,7 @@ public class FolderList extends K9ListActivity {
Locale locale = Locale.getDefault();
if ((searchTerm == null) || (searchTerm.length() == 0)) {
- ArrayList list = new ArrayList(mFolders);
+ List list = new ArrayList(mFolders);
results.values = list;
results.count = list.size();
} else {
@@ -1184,7 +1184,7 @@ public class FolderList extends K9ListActivity {
final String[] words = searchTermString.split(" ");
final int wordCount = words.length;
- final ArrayList newValues = new ArrayList();
+ final List newValues = new ArrayList();
for (final FolderInfoHolder value : mFolders) {
if (value.displayName == null) {
diff --git a/src/com/fsck/k9/activity/FolderListFilter.java b/src/com/fsck/k9/activity/FolderListFilter.java
index 30a012600..ec01589c3 100644
--- a/src/com/fsck/k9/activity/FolderListFilter.java
+++ b/src/com/fsck/k9/activity/FolderListFilter.java
@@ -29,7 +29,7 @@ public class FolderListFilter extends Filter {
/**
* All folders.
*/
- private ArrayList mOriginalValues = null;
+ private List mOriginalValues = null;
/**
* Create a filter for a list of folders.
@@ -62,7 +62,7 @@ public class FolderListFilter extends Filter {
Locale locale = Locale.getDefault();
if ((searchTerm == null) || (searchTerm.length() == 0)) {
- ArrayList list = new ArrayList(mOriginalValues);
+ List list = new ArrayList(mOriginalValues);
results.values = list;
results.count = list.size();
} else {
@@ -70,9 +70,9 @@ public class FolderListFilter extends Filter {
final String[] words = searchTermString.split(" ");
final int wordCount = words.length;
- final ArrayList values = mOriginalValues;
+ final List values = mOriginalValues;
- final ArrayList newValues = new ArrayList();
+ final List newValues = new ArrayList();
for (final T value : values) {
final String valueText = value.toString().toLowerCase(locale);
diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java
index 05170db2e..4a0ab4cfa 100644
--- a/src/com/fsck/k9/activity/MessageCompose.java
+++ b/src/com/fsck/k9/activity/MessageCompose.java
@@ -1013,7 +1013,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
addAttachment(stream, type);
}
} else {
- ArrayList list = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
+ List list = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
if (list != null) {
for (Parcelable parcelable : list) {
Uri stream = (Uri) parcelable;
@@ -1155,7 +1155,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
- ArrayList attachments = new ArrayList();
+ List attachments = new ArrayList();
for (int i = 0, count = mAttachments.getChildCount(); i < count; i++) {
View view = mAttachments.getChildAt(i);
Attachment attachment = (Attachment) view.getTag();
@@ -1164,7 +1164,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
outState.putInt(STATE_KEY_NUM_ATTACHMENTS_LOADING, mNumAttachmentsLoading);
outState.putString(STATE_KEY_WAITING_FOR_ATTACHMENTS, mWaitingForAttachments.name());
- outState.putParcelableArrayList(STATE_KEY_ATTACHMENTS, attachments);
+ outState.putParcelableArrayList(STATE_KEY_ATTACHMENTS, Utility.toArrayList(attachments));
outState.putBoolean(STATE_KEY_CC_SHOWN, mCcWrapper.getVisibility() == View.VISIBLE);
outState.putBoolean(STATE_KEY_BCC_SHOWN, mBccWrapper.getVisibility() == View.VISIBLE);
outState.putSerializable(STATE_KEY_QUOTED_TEXT_MODE, mQuotedTextMode);
@@ -1199,7 +1199,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
"\" from saved instance state", e);
}
- ArrayList attachments = savedInstanceState.getParcelableArrayList(STATE_KEY_ATTACHMENTS);
+ List attachments = savedInstanceState.getParcelableArrayList(STATE_KEY_ATTACHMENTS);
for (Attachment attachment : attachments) {
addAttachmentView(attachment);
if (attachment.loaderId > mMaxLoaderId) {
@@ -1808,7 +1808,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
String[] emailsArray = null;
if (mEncryptCheckbox.isChecked()) {
// get emails as array
- ArrayList emails = new ArrayList();
+ List emails = new ArrayList();
for (Address address : getRecipientAddresses()) {
emails.add(address.getAddress());
diff --git a/src/com/fsck/k9/activity/NotificationDeleteConfirmation.java b/src/com/fsck/k9/activity/NotificationDeleteConfirmation.java
index a740768c6..9a070ed39 100644
--- a/src/com/fsck/k9/activity/NotificationDeleteConfirmation.java
+++ b/src/com/fsck/k9/activity/NotificationDeleteConfirmation.java
@@ -1,6 +1,7 @@
package com.fsck.k9.activity;
import java.util.ArrayList;
+import java.util.List;
import android.app.Activity;
import android.app.AlertDialog;
@@ -14,6 +15,7 @@ import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
+import com.fsck.k9.helper.Utility;
import com.fsck.k9.service.NotificationActionService;
public class NotificationDeleteConfirmation extends Activity {
@@ -23,12 +25,12 @@ public class NotificationDeleteConfirmation extends Activity {
private final static int DIALOG_CONFIRM = 1;
private Account mAccount;
- private ArrayList mMessageRefs;
+ private List mMessageRefs;
- public static PendingIntent getIntent(Context context, final Account account, final ArrayList refs) {
+ public static PendingIntent getIntent(Context context, final Account account, final List refs) {
Intent i = new Intent(context, NotificationDeleteConfirmation.class);
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
- i.putExtra(EXTRA_MESSAGE_LIST, refs);
+ i.putExtra(EXTRA_MESSAGE_LIST, Utility.toSerializableList(refs));
i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
return PendingIntent.getActivity(context, account.getAccountNumber(), i, PendingIntent.FLAG_UPDATE_CURRENT);
diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java
index d71c06997..49ecfea36 100644
--- a/src/com/fsck/k9/controller/MessagingController.java
+++ b/src/com/fsck/k9/controller/MessagingController.java
@@ -299,8 +299,8 @@ public class MessagingController implements Runnable {
*
* @return Message reference list
*/
- public ArrayList getAllMessageRefs() {
- ArrayList refs = new ArrayList();
+ public List getAllMessageRefs() {
+ List refs = new ArrayList();
for (Message m : messages) {
refs.add(m.makeMessageReference());
}
@@ -993,7 +993,7 @@ public class MessagingController implements Runnable {
localFolder.open(Folder.OPEN_MODE_RW);
localFolder.updateLastUid();
Message[] localMessages = localFolder.getMessages(null);
- HashMap localUidMap = new HashMap();
+ Map localUidMap = new HashMap();
for (Message message : localMessages) {
localUidMap.put(message.getUid(), message);
}
@@ -1059,8 +1059,8 @@ public class MessagingController implements Runnable {
}
Message[] remoteMessageArray = EMPTY_MESSAGE_ARRAY;
- final ArrayList remoteMessages = new ArrayList();
- HashMap remoteUidMap = new HashMap();
+ final List remoteMessages = new ArrayList();
+ Map remoteUidMap = new HashMap();
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "SYNC: Remote message count for folder " + folder + " is " + remoteMessageCount);
@@ -1117,7 +1117,7 @@ public class MessagingController implements Runnable {
* Remove any messages that are in the local store but no longer on the remote store or are too old
*/
if (account.syncRemoteDeletions()) {
- ArrayList destroyMessages = new ArrayList();
+ List destroyMessages = new ArrayList();
for (Message localMessage : localMessages) {
if (remoteUidMap.get(localMessage.getUid()) == null) {
destroyMessages.add(localMessage);
@@ -1280,7 +1280,7 @@ public class MessagingController implements Runnable {
Log.e(K9.LOG_TAG, "Unable to getUnreadMessageCount for account: " + account, e);
}
- ArrayList syncFlagMessages = new ArrayList();
+ List syncFlagMessages = new ArrayList();
List unsyncedMessages = new ArrayList();
final AtomicInteger newMessages = new AtomicInteger(0);
@@ -1300,8 +1300,8 @@ public class MessagingController implements Runnable {
Log.d(K9.LOG_TAG, "SYNC: Have " + unsyncedMessages.size() + " unsynced messages");
messages.clear();
- final ArrayList largeMessages = new ArrayList();
- final ArrayList smallMessages = new ArrayList();
+ final List largeMessages = new ArrayList();
+ final List smallMessages = new ArrayList();
if (!unsyncedMessages.isEmpty()) {
/*
@@ -1416,7 +1416,7 @@ public class MessagingController implements Runnable {
final Folder remoteFolder,
final Account account,
final List unsyncedMessages,
- final ArrayList syncFlagMessages,
+ final List syncFlagMessages,
boolean flagSyncOnly) throws MessagingException {
if (message.isSet(Flag.DELETED)) {
syncFlagMessages.add(message);
@@ -1475,8 +1475,8 @@ public class MessagingController implements Runnable {
private void fetchUnsyncedMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder,
List unsyncedMessages,
- final ArrayList smallMessages,
- final ArrayList largeMessages,
+ final List smallMessages,
+ final List largeMessages,
final AtomicInteger progress,
final int todo,
FetchProfile fp) throws MessagingException {
@@ -1611,7 +1611,7 @@ public class MessagingController implements Runnable {
private void downloadSmallMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder,
- ArrayList smallMessages,
+ List smallMessages,
final AtomicInteger progress,
final int unreadBeforeStart,
final AtomicInteger newMessages,
@@ -1690,7 +1690,7 @@ public class MessagingController implements Runnable {
private void downloadLargeMessages(final Account account, final Folder remoteFolder,
final LocalFolder localFolder,
- ArrayList largeMessages,
+ List largeMessages,
final AtomicInteger progress,
final int unreadBeforeStart,
final AtomicInteger newMessages,
@@ -1815,7 +1815,7 @@ public class MessagingController implements Runnable {
private void refreshLocalMessageFlags(final Account account, final Folder remoteFolder,
final LocalFolder localFolder,
- ArrayList syncFlagMessages,
+ List syncFlagMessages,
final AtomicInteger progress,
final int todo
) throws MessagingException {
@@ -1952,7 +1952,7 @@ public class MessagingController implements Runnable {
private void processPendingCommandsSynchronous(Account account) throws MessagingException {
LocalStore localStore = account.getLocalStore();
- ArrayList commands = localStore.getPendingCommands();
+ List commands = localStore.getPendingCommands();
int progress = 0;
int todo = commands.size();
@@ -4863,7 +4863,7 @@ public class MessagingController implements Runnable {
String accountDescr = (account.getDescription() != null) ?
account.getDescription() : account.getEmail();
- final ArrayList allRefs = data.getAllMessageRefs();
+ final List allRefs = data.getAllMessageRefs();
if (platformSupportsExtendedNotifications() && !privacyModeEnabled) {
if (newMessages > 1) {
@@ -5424,7 +5424,7 @@ public class MessagingController implements Runnable {
return taccount.getDescription() + ":" + tfolderName;
}
static class MemorizingListener extends MessagingListener {
- HashMap memories = new HashMap(31);
+ Map memories = new HashMap(31);
Memory getMemory(Account account, String folderName) {
Memory memory = memories.get(getMemoryKey(account, folderName));
diff --git a/src/com/fsck/k9/fragment/MessageListFragment.java b/src/com/fsck/k9/fragment/MessageListFragment.java
index e72da130d..b73bfa495 100644
--- a/src/com/fsck/k9/fragment/MessageListFragment.java
+++ b/src/com/fsck/k9/fragment/MessageListFragment.java
@@ -2995,8 +2995,8 @@ public class MessageListFragment extends Fragment implements OnItemClickListener
super.onStop();
}
- public ArrayList getMessageReferences() {
- ArrayList messageRefs = new ArrayList();
+ public List getMessageReferences() {
+ List messageRefs = new ArrayList();
for (int i = 0, len = mAdapter.getCount(); i < len; i++) {
Cursor cursor = (Cursor) mAdapter.getItem(i);
diff --git a/src/com/fsck/k9/helper/Contacts.java b/src/com/fsck/k9/helper/Contacts.java
index 56866c21c..75c084aba 100644
--- a/src/com/fsck/k9/helper/Contacts.java
+++ b/src/com/fsck/k9/helper/Contacts.java
@@ -13,6 +13,7 @@ import com.fsck.k9.K9;
import com.fsck.k9.mail.Address;
import java.util.ArrayList;
+import java.util.List;
/**
* Helper class to access the contacts stored on the device.
@@ -276,7 +277,7 @@ public class Contacts {
*/
public ContactItem extractInfoFromContactPickerIntent(final Intent intent) {
Cursor cursor = null;
- ArrayList email = new ArrayList();
+ List email = new ArrayList();
try {
Uri result = intent.getData();
diff --git a/src/com/fsck/k9/helper/Utility.java b/src/com/fsck/k9/helper/Utility.java
index ae8d9bb71..c450ad448 100644
--- a/src/com/fsck/k9/helper/Utility.java
+++ b/src/com/fsck/k9/helper/Utility.java
@@ -20,10 +20,13 @@ import com.fsck.k9.mail.filter.Base64;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
+import java.io.Serializable;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -704,4 +707,23 @@ public class Utility {
}
return sMainThreadHandler;
}
+
+ public static Serializable toSerializableList(List list) {
+ return list instanceof Serializable ?
+ (Serializable) list :
+ new ArrayList(list);
+ }
+
+ public static ArrayList toArrayList(List list) {
+ return list instanceof ArrayList ?
+ (ArrayList) list :
+ new ArrayList(list);
+ }
+
+
+ public static Serializable toSerializableConcurrentMap(ConcurrentMap list) {
+ return list instanceof ConcurrentHashMap ?
+ (ConcurrentHashMap) list :
+ new ConcurrentHashMap(list);
+ }
}
diff --git a/src/com/fsck/k9/mail/Address.java b/src/com/fsck/k9/mail/Address.java
index b5bdcc854..ef79b3790 100644
--- a/src/com/fsck/k9/mail/Address.java
+++ b/src/com/fsck/k9/mail/Address.java
@@ -315,7 +315,7 @@ public class Address {
if (addressList == null) {
return new Address[] { };
}
- ArrayList addresses = new ArrayList();
+ List addresses = new ArrayList();
int length = addressList.length();
int pairStartIndex = 0;
int pairEndIndex = 0;
diff --git a/src/com/fsck/k9/mail/Multipart.java b/src/com/fsck/k9/mail/Multipart.java
index 88651485a..567b0902d 100644
--- a/src/com/fsck/k9/mail/Multipart.java
+++ b/src/com/fsck/k9/mail/Multipart.java
@@ -13,7 +13,7 @@ import com.fsck.k9.mail.internet.TextBody;
public abstract class Multipart implements CompositeBody {
private Part mParent;
- private final ArrayList mParts = new ArrayList();
+ private final List mParts = new ArrayList();
private String mContentType;
diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java
index 659b81161..5be7d7011 100644
--- a/src/com/fsck/k9/mail/Store.java
+++ b/src/com/fsck/k9/mail/Store.java
@@ -3,7 +3,9 @@ package com.fsck.k9.mail;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
import android.app.Application;
import android.content.Context;
@@ -33,19 +35,19 @@ public abstract class Store {
/**
* Remote stores indexed by Uri.
*/
- private static HashMap sStores = new HashMap();
+ private static Map sStores = new HashMap();
/**
* Local stores indexed by UUID because the Uri may change due to migration to/from SD-card.
*/
- private static ConcurrentHashMap sLocalStores = new ConcurrentHashMap();
+ private static ConcurrentMap sLocalStores = new ConcurrentHashMap();
/**
* Lock objects indexed by account UUID.
*
* @see #getLocalInstance(Account, Application)
*/
- private static ConcurrentHashMap sAccountLocks = new ConcurrentHashMap();
+ private static ConcurrentMap sAccountLocks = new ConcurrentHashMap();
/**
* Get an instance of a remote mail store.
diff --git a/src/com/fsck/k9/mail/internet/MimeHeader.java b/src/com/fsck/k9/mail/internet/MimeHeader.java
index dd6445755..12bd5fd31 100644
--- a/src/com/fsck/k9/mail/internet/MimeHeader.java
+++ b/src/com/fsck/k9/mail/internet/MimeHeader.java
@@ -36,7 +36,7 @@ public class MimeHeader {
HEADER_ANDROID_ATTACHMENT_STORE_DATA
};
- private ArrayList mFields = new ArrayList();
+ private List mFields = new ArrayList();
private String mCharset = null;
public void clear() {
@@ -72,7 +72,7 @@ public class MimeHeader {
}
public String[] getHeader(String name) {
- ArrayList values = new ArrayList();
+ List values = new ArrayList();
for (Field field : mFields) {
if (field.name.equalsIgnoreCase(name)) {
values.add(field.value);
@@ -85,7 +85,7 @@ public class MimeHeader {
}
public void removeHeader(String name) {
- ArrayList removeFields = new ArrayList();
+ List removeFields = new ArrayList();
for (Field field : mFields) {
if (field.name.equalsIgnoreCase(name)) {
removeFields.add(field);
diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java
index f66215e0b..1987e0b5a 100644
--- a/src/com/fsck/k9/mail/store/ImapStore.java
+++ b/src/com/fsck/k9/mail/store/ImapStore.java
@@ -1356,9 +1356,9 @@ public class ImapStore extends Store {
private List search(ImapSearcher searcher, MessageRetrievalListener listener) throws MessagingException {
checkOpen(); //only need READ access
- ArrayList messages = new ArrayList