diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java
index 54297b580..ce565138b 100644
--- a/src/com/fsck/k9/Account.java
+++ b/src/com/fsck/k9/Account.java
@@ -31,9 +31,9 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.Folder.FolderClass;
-import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.StorageManager.StorageProvider;
+import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.provider.EmailProvider;
import com.fsck.k9.provider.EmailProvider.StatsColumns;
import com.fsck.k9.search.ConditionsTreeNode;
@@ -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;
@@ -338,7 +338,7 @@ public class Account implements BaseAccount {
* Pick a nice Android guidelines color if we haven't used them all yet.
*/
private int pickColor(Context context) {
- Account[] accounts = Preferences.getPreferences(context).getAccounts();
+ List accounts = Preferences.getPreferences(context).getAccounts();
List availableColors = new ArrayList(PREDEFINED_COLORS.length);
Collections.addAll(availableColors, PREDEFINED_COLORS);
@@ -620,8 +620,8 @@ public class Account implements BaseAccount {
}
public static List getExistingAccountNumbers(Preferences preferences) {
- Account[] accounts = preferences.getAccounts();
- List accountNumbers = new ArrayList(accounts.length);
+ List accounts = preferences.getAccounts();
+ List accountNumbers = new ArrayList(accounts.size());
for (Account a : accounts) {
accountNumbers.add(a.getAccountNumber());
}
@@ -679,10 +679,10 @@ public class Account implements BaseAccount {
*
* I bet there is a much smarter way to do this. Anyone like to suggest it?
*/
- Account[] accounts = preferences.getAccounts();
- int[] accountNumbers = new int[accounts.length];
- for (int i = 0; i < accounts.length; i++) {
- accountNumbers[i] = accounts[i].getAccountNumber();
+ List accounts = preferences.getAccounts();
+ int[] accountNumbers = new int[accounts.size()];
+ for (int i = 0; i < accounts.size(); i++) {
+ accountNumbers[i] = accounts.get(i).getAccountNumber();
}
Arrays.sort(accountNumbers);
for (int accountNumber : accountNumbers) {
diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java
index 593bc07a9..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;
@@ -35,7 +36,7 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.internet.BinaryTempFileBody;
-import com.fsck.k9.mail.store.LocalStore;
+import com.fsck.k9.mail.store.local.LocalStore;
import com.fsck.k9.provider.UnreadWidgetProvider;
import com.fsck.k9.security.LocalKeyStore;
import com.fsck.k9.service.BootReceiver;
@@ -88,7 +89,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()}
@@ -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/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/Preferences.java b/src/com/fsck/k9/Preferences.java
index 51b83d873..5fd4929a3 100644
--- a/src/com/fsck/k9/Preferences.java
+++ b/src/com/fsck/k9/Preferences.java
@@ -3,6 +3,7 @@ package com.fsck.k9;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
@@ -18,11 +19,6 @@ import com.fsck.k9.preferences.Storage;
public class Preferences {
- /**
- * Immutable empty {@link Account} array
- */
- private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[0];
-
private static Preferences preferences;
public static synchronized Preferences getPreferences(Context context) {
@@ -43,7 +39,7 @@ public class Preferences {
private Preferences(Context context) {
mStorage = Storage.getStorage(context);
mContext = context;
- if (mStorage.size() == 0) {
+ if (mStorage.isEmpty()) {
Log.i(K9.LOG_TAG, "Preferences storage is zero-size, importing from Android-style preferences");
Editor editor = mStorage.edit();
editor.copy(context.getSharedPreferences("AndroidMail.Main", Context.MODE_PRIVATE));
@@ -75,12 +71,12 @@ public class Preferences {
* registered the method returns an empty array.
* @return all accounts
*/
- public synchronized Account[] getAccounts() {
+ public synchronized List getAccounts() {
if (accounts == null) {
loadAccounts();
}
- return accountsInOrder.toArray(EMPTY_ACCOUNT_ARRAY);
+ return Collections.unmodifiableList(accountsInOrder);
}
/**
@@ -89,7 +85,7 @@ public class Preferences {
* @return all accounts with {@link Account#isAvailable(Context)}
*/
public synchronized Collection getAvailableAccounts() {
- Account[] allAccounts = getAccounts();
+ List allAccounts = getAccounts();
Collection retval = new ArrayList(accounts.size());
for (Account account : allAccounts) {
if (account.isEnabled() && account.isAvailable(mContext)) {
diff --git a/src/com/fsck/k9/activity/AccountList.java b/src/com/fsck/k9/activity/AccountList.java
index d5f29b32e..571dfc907 100644
--- a/src/com/fsck/k9/activity/AccountList.java
+++ b/src/com/fsck/k9/activity/AccountList.java
@@ -68,7 +68,7 @@ public abstract class AccountList extends K9ListActivity implements OnItemClickL
* @param realAccounts
* An array of accounts to display.
*/
- public void populateListView(Account[] realAccounts) {
+ public void populateListView(List realAccounts) {
List accounts = new ArrayList();
if (displaySpecialAccounts() && !K9.isHideSpecialAccounts()) {
@@ -79,7 +79,7 @@ public abstract class AccountList extends K9ListActivity implements OnItemClickL
accounts.add(allMessagesAccount);
}
- accounts.addAll(Arrays.asList(realAccounts));
+ accounts.addAll(realAccounts);
AccountsAdapter adapter = new AccountsAdapter(accounts);
ListView listView = getListView();
listView.setAdapter(adapter);
@@ -169,15 +169,15 @@ public abstract class AccountList extends K9ListActivity implements OnItemClickL
/**
* Load accounts in a background thread
*/
- class LoadAccounts extends AsyncTask {
+ class LoadAccounts extends AsyncTask> {
@Override
- protected Account[] doInBackground(Void... params) {
- Account[] accounts = Preferences.getPreferences(getApplicationContext()).getAccounts();
+ protected List doInBackground(Void... params) {
+ List accounts = Preferences.getPreferences(getApplicationContext()).getAccounts();
return accounts;
}
@Override
- protected void onPostExecute(Account[] accounts) {
+ protected void onPostExecute(List accounts) {
populateListView(accounts);
}
}
diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java
index 76cfe3abb..738ee32a3 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;
@@ -100,11 +102,6 @@ import de.cketti.library.changelog.ChangeLog;
public class Accounts extends K9ListActivity implements OnItemClickListener {
- /**
- * Immutable empty {@link BaseAccount} array
- */
- private static final BaseAccount[] EMPTY_BASE_ACCOUNT_ARRAY = new BaseAccount[0];
-
/**
* URL used to open Android Market application
*/
@@ -120,9 +117,12 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
private static final int DIALOG_RECREATE_ACCOUNT = 3;
private static final int DIALOG_NO_FILE_MANAGER = 4;
+ /*
+ * Must be serializable hence implementation class used for declaration.
+ */
private ConcurrentHashMap accountStats = new ConcurrentHashMap();
- private ConcurrentHashMap pendingWork = new ConcurrentHashMap();
+ private ConcurrentMap pendingWork = new ConcurrentHashMap();
private BaseAccount mSelectedContextAccount;
private int mUnreadMessageCount = 0;
@@ -398,14 +398,14 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
createSpecialAccounts();
}
- Account[] accounts = Preferences.getPreferences(this).getAccounts();
+ List accounts = Preferences.getPreferences(this).getAccounts();
Intent intent = getIntent();
//onNewIntent(intent);
// see if we should show the welcome message
if (ACTION_IMPORT_SETTINGS.equals(intent.getAction())) {
onImport();
- } else if (accounts.length < 1) {
+ } else if (accounts.size() < 1) {
WelcomeMessage.showWelcomeMessage(this);
finish();
return;
@@ -421,7 +421,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
onOpenAccount(mUnifiedInboxAccount);
finish();
return;
- } else if (startup && accounts.length == 1 && onOpenAccount(accounts[0])) {
+ } else if (startup && accounts.size() == 1 && onOpenAccount(accounts.get(0))) {
finish();
return;
}
@@ -541,18 +541,18 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
return retain;
}
- private BaseAccount[] accounts = new BaseAccount[0];
+ private List accounts = new ArrayList();
private enum ACCOUNT_LOCATION {
TOP, MIDDLE, BOTTOM;
}
private EnumSet accountLocation(BaseAccount account) {
EnumSet accountLocation = EnumSet.of(ACCOUNT_LOCATION.MIDDLE);
- if (accounts.length > 0) {
- if (accounts[0].equals(account)) {
+ if (accounts.size() > 0) {
+ if (accounts.get(0).equals(account)) {
accountLocation.remove(ACCOUNT_LOCATION.MIDDLE);
accountLocation.add(ACCOUNT_LOCATION.TOP);
}
- if (accounts[accounts.length - 1].equals(account)) {
+ if (accounts.get(accounts.size() - 1).equals(account)) {
accountLocation.remove(ACCOUNT_LOCATION.MIDDLE);
accountLocation.add(ACCOUNT_LOCATION.BOTTOM);
}
@@ -562,7 +562,8 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
private void refresh() {
- accounts = Preferences.getPreferences(this).getAccounts();
+ accounts.clear();
+ accounts.addAll(Preferences.getPreferences(this).getAccounts());
// see if we should show the welcome message
// if (accounts.length < 1) {
@@ -571,22 +572,22 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
// }
List newAccounts;
- if (!K9.isHideSpecialAccounts() && accounts.length > 0) {
+ if (!K9.isHideSpecialAccounts() && accounts.size() > 0) {
if (mUnifiedInboxAccount == null || mAllMessagesAccount == null) {
createSpecialAccounts();
}
- newAccounts = new ArrayList(accounts.length +
+ newAccounts = new ArrayList(accounts.size() +
SPECIAL_ACCOUNTS_COUNT);
newAccounts.add(mUnifiedInboxAccount);
newAccounts.add(mAllMessagesAccount);
} else {
- newAccounts = new ArrayList(accounts.length);
+ newAccounts = new ArrayList(accounts.size());
}
- newAccounts.addAll(Arrays.asList(accounts));
+ newAccounts.addAll(accounts);
- mAdapter = new AccountsAdapter(newAccounts.toArray(EMPTY_BASE_ACCOUNT_ARRAY));
+ mAdapter = new AccountsAdapter(newAccounts);
getListView().setAdapter(mAdapter);
if (!newAccounts.isEmpty()) {
mHandler.progress(Window.PROGRESS_START);
@@ -1735,7 +1736,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener {
}
class AccountsAdapter extends ArrayAdapter {
- public AccountsAdapter(BaseAccount[] accounts) {
+ public AccountsAdapter(List accounts) {
super(Accounts.this, 0, accounts);
}
diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java
index 3061b2223..83e8070b8 100644
--- a/src/com/fsck/k9/activity/ChooseFolder.java
+++ b/src/com/fsck/k9/activity/ChooseFolder.java
@@ -261,7 +261,7 @@ public class ChooseFolder extends K9ListActivity {
mHandler.progress(false);
}
@Override
- public void listFolders(Account account, Folder[] folders) {
+ public void listFolders(Account account, List extends Folder> folders) {
if (!account.equals(mAccount)) {
return;
}
diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java
index 1270c724a..670419cf1 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.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;
@@ -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();
@@ -740,7 +740,7 @@ public class FolderList extends K9ListActivity {
}
@Override
- public void listFolders(Account account, Folder[] folders) {
+ public void listFolders(Account account, List extends Folder> folders) {
if (account.equals(mAccount)) {
List newFolders = new LinkedList();
@@ -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 9c473629a..5b975247c 100644
--- a/src/com/fsck/k9/activity/MessageCompose.java
+++ b/src/com/fsck/k9/activity/MessageCompose.java
@@ -5,9 +5,11 @@ 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;
+import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
@@ -107,9 +109,9 @@ import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mail.internet.TextBody;
import com.fsck.k9.mail.internet.TextBodyBuilder;
-import com.fsck.k9.mail.store.LocalStore.LocalAttachmentBody;
-import com.fsck.k9.mail.store.LocalStore.TempFileBody;
-import com.fsck.k9.mail.store.LocalStore.TempFileMessageBody;
+import com.fsck.k9.mail.store.local.LocalAttachmentBody;
+import com.fsck.k9.mail.store.local.TempFileBody;
+import com.fsck.k9.mail.store.local.TempFileMessageBody;
import com.fsck.k9.view.MessageWebView;
import org.apache.james.mime4j.codec.EncoderUtil;
@@ -190,8 +192,6 @@ public class MessageCompose extends K9Activity implements OnClickListener,
private static final int CONTACT_PICKER_CC2 = 8;
private static final int CONTACT_PICKER_BCC2 = 9;
- private static final Account[] EMPTY_ACCOUNT_ARRAY = new Account[0];
-
private static final int REQUEST_CODE_SIGN_ENCRYPT = 12;
/**
@@ -1012,7 +1012,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;
@@ -1056,7 +1056,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
}
private boolean addRecipients(TextView view, List recipients) {
- if (recipients == null || recipients.size() == 0) {
+ if (recipients == null || recipients.isEmpty()) {
return false;
}
@@ -1198,7 +1198,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) {
@@ -1807,7 +1807,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());
@@ -1890,13 +1890,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) {
@@ -3921,7 +3915,7 @@ public class MessageCompose extends K9Activity implements OnClickListener,
List
*/
+ @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 b5f563a1b..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);
@@ -28,9 +28,9 @@ public class MessagingException extends Exception {
return permanentFailure;
}
+ //TODO setters in Exception are bad style, remove (it's nearly unused anyway)
public void setPermanentFailure(boolean permanentFailure) {
this.permanentFailure = permanentFailure;
}
-
}
diff --git a/src/com/fsck/k9/mail/Multipart.java b/src/com/fsck/k9/mail/Multipart.java
index cab1fa280..567b0902d 100644
--- a/src/com/fsck/k9/mail/Multipart.java
+++ b/src/com/fsck/k9/mail/Multipart.java
@@ -2,6 +2,8 @@
package com.fsck.k9.mail;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import org.apache.james.mime4j.util.MimeUtil;
@@ -9,26 +11,25 @@ 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();
+ private final List mParts = new ArrayList();
- protected String mContentType;
+ private String mContentType;
public void addBodyPart(BodyPart part) {
mParts.add(part);
part.setParent(this);
}
- public void addBodyPart(BodyPart part, int index) {
- mParts.add(index, part);
- part.setParent(this);
- }
-
public BodyPart getBodyPart(int index) {
return mParts.get(index);
}
+ public List getBodyParts() {
+ return Collections.unmodifiableList(mParts);
+ }
+
public String getContentType() {
return mContentType;
}
@@ -37,16 +38,6 @@ public abstract class Multipart implements CompositeBody {
return mParts.size();
}
- public boolean removeBodyPart(BodyPart part) {
- part.setParent(null);
- return mParts.remove(part);
- }
-
- public void removeBodyPart(int index) {
- mParts.get(index).setParent(null);
- mParts.remove(index);
- }
-
public Part getParent() {
return mParent;
}
@@ -55,6 +46,7 @@ public abstract class Multipart implements CompositeBody {
this.mParent = parent;
}
+ @Override
public void setEncoding(String encoding) throws MessagingException {
if (!MimeUtil.ENC_7BIT.equalsIgnoreCase(encoding)
&& !MimeUtil.ENC_8BIT.equalsIgnoreCase(encoding)) {
diff --git a/src/com/fsck/k9/mail/Part.java b/src/com/fsck/k9/mail/Part.java
index 731861b76..38c1ad9da 100644
--- a/src/com/fsck/k9/mail/Part.java
+++ b/src/com/fsck/k9/mail/Part.java
@@ -21,8 +21,6 @@ public interface Part {
public String[] getHeader(String name) throws MessagingException;
- public int getSize();
-
public boolean isMimeType(String mimeType) throws MessagingException;
public String getMimeType() throws MessagingException;
@@ -42,5 +40,6 @@ public interface Part {
* @throws MessagingException
*
*/
+ //TODO perhaps it would be clearer to use a flag "force7bit" in writeTo
public abstract void setUsing7bitTransport() throws MessagingException;
}
diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java
index 45efb2a34..7dbfade7e 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;
@@ -12,9 +14,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;
@@ -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.
@@ -242,7 +244,7 @@ public abstract class Store {
return true;
}
- public void sendMessages(Message[] messages) throws MessagingException {
+ public void sendMessages(List extends Message> messages) throws MessagingException {
}
public Pusher getPusher(PushReceiver receiver) {
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/internet/MimeBodyPart.java b/src/com/fsck/k9/mail/internet/MimeBodyPart.java
index 60e3b4cca..c4bca428a 100644
--- a/src/com/fsck/k9/mail/internet/MimeBodyPart.java
+++ b/src/com/fsck/k9/mail/internet/MimeBodyPart.java
@@ -20,9 +20,8 @@ import org.apache.james.mime4j.util.MimeUtil;
* Message.
*/
public class MimeBodyPart extends BodyPart {
- protected MimeHeader mHeader = new MimeHeader();
- protected Body mBody;
- protected int mSize;
+ private final MimeHeader mHeader = new MimeHeader();
+ private Body mBody;
public MimeBodyPart() throws MessagingException {
this(null);
@@ -39,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) {
@@ -94,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) {
@@ -117,21 +125,20 @@ 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);
}
- public int getSize() {
- return mSize;
- }
-
/**
* 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..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
};
- protected 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);
@@ -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 2f69cba39..75fa217d8 100644
--- a/src/com/fsck/k9/mail/internet/MimeMessage.java
+++ b/src/com/fsck/k9/mail/internet/MimeMessage.java
@@ -41,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;
@@ -49,33 +49,19 @@ 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() {
}
- /**
- * 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 +74,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 +111,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);
-
}
}
@@ -177,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;
}
@@ -419,7 +414,7 @@ public class MimeMessage extends Message {
}
}
- protected String getFirstHeader(String name) {
+ private String getFirstHeader(String name) {
return mHeader.getFirstHeader(name);
}
@@ -448,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);
@@ -459,6 +455,7 @@ public class MimeMessage extends Message {
}
}
+ @Override
public InputStream getInputStream() throws MessagingException {
return null;
}
@@ -482,7 +479,7 @@ public class MimeMessage extends Message {
}
}
- class MimeMessageBuilder implements ContentHandler {
+ private class MimeMessageBuilder implements ContentHandler {
private final LinkedList stack = new LinkedList();
public MimeMessageBuilder() {
@@ -495,6 +492,7 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void startMessage() {
if (stack.isEmpty()) {
stack.addFirst(MimeMessage.this);
@@ -510,22 +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);
@@ -539,6 +538,7 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void body(BodyDescriptor bd, InputStream in) throws IOException {
expect(Part.class);
try {
@@ -550,10 +550,12 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void endMultipart() {
stack.removeFirst();
}
+ @Override
public void startBodyPart() {
expect(MimeMultipart.class);
@@ -566,21 +568,13 @@ public class MimeMessage extends Message {
}
}
+ @Override
public void endBodyPart() {
expect(BodyPart.class);
stack.removeFirst();
}
- public void epilogue(InputStream is) throws IOException {
- expect(MimeMultipart.class);
- StringBuilder sb = new StringBuilder();
- int b;
- while ((b = is.read()) != -1) {
- sb.append((char)b);
- }
- // ((Multipart) stack.peek()).setEpilogue(sb.toString());
- }
-
+ @Override
public void preamble(InputStream is) throws IOException {
expect(MimeMultipart.class);
StringBuilder sb = new StringBuilder();
@@ -589,9 +583,13 @@ public class MimeMessage extends Message {
sb.append((char)b);
}
((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");
}
@@ -641,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..124ca5e4c 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);
@@ -74,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("--");
@@ -96,13 +92,14 @@ public class MimeMultipart extends Multipart {
writer.flush();
}
+ @Override
public InputStream getInputStream() throws MessagingException {
return null;
}
@Override
public void setUsing7bitTransport() throws MessagingException {
- for (BodyPart part : mParts) {
+ for (BodyPart part : getBodyParts()) {
part.setUsing7bitTransport();
}
}
diff --git a/src/com/fsck/k9/mail/internet/MimeUtility.java b/src/com/fsck/k9/mail/internet/MimeUtility.java
index c97e25df6..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;
}
@@ -993,8 +989,7 @@ public class MimeUtility {
throws MessagingException {
if (part.getBody() instanceof Multipart) {
Multipart multipart = (Multipart)part.getBody();
- for (int i = 0, count = multipart.getCount(); i < count; i++) {
- BodyPart bodyPart = multipart.getBodyPart(i);
+ for (BodyPart bodyPart : multipart.getBodyParts()) {
Part ret = findFirstPartByMimeType(bodyPart, mimeType);
if (ret != null) {
return ret;
@@ -1006,28 +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 (int i = 0, count = multipart.getCount(); i < count; i++) {
- BodyPart bodyPart = multipart.getBodyPart(i);
- 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.
@@ -1468,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();
@@ -1490,9 +1463,7 @@ public class MimeUtility {
}
} else {
// For all other multipart parts we recurse to grab all viewable children.
- int childCount = multipart.getCount();
- for (int i = 0; i < childCount; i++) {
- Part bodyPart = multipart.getBodyPart(i);
+ for (Part bodyPart : multipart.getBodyParts()) {
viewables.addAll(getViewables(bodyPart, attachments));
}
}
@@ -1547,9 +1518,7 @@ public class MimeUtility {
throws MessagingException {
List viewables = new ArrayList();
- int childCount = multipart.getCount();
- for (int i = 0; i < childCount; i++) {
- Part part = multipart.getBodyPart(i);
+ for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
@@ -1612,9 +1581,7 @@ public class MimeUtility {
List viewables = new ArrayList();
boolean partFound = false;
- int childCount = multipart.getCount();
- for (int i = 0; i < childCount; i++) {
- Part part = multipart.getBodyPart(i);
+ for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
@@ -1698,9 +1665,7 @@ public class MimeUtility {
*/
private static void findAttachments(Multipart multipart, Set knownTextParts,
List attachments) {
- int childCount = multipart.getCount();
- for (int i = 0; i < childCount; i++) {
- Part part = multipart.getBodyPart(i);
+ for (Part part : multipart.getBodyParts()) {
Body body = part.getBody();
if (body instanceof Multipart) {
Multipart innerMultipart = (Multipart) body;
@@ -2049,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;
@@ -2137,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)) {
@@ -3422,8 +3387,7 @@ public class MimeUtility {
} else if (part.isMimeType("multipart/alternative") &&
firstBody instanceof MimeMultipart) {
MimeMultipart multipart = (MimeMultipart) firstBody;
- for (int i = 0, count = multipart.getCount(); i < count; i++) {
- BodyPart bodyPart = multipart.getBodyPart(i);
+ for (BodyPart bodyPart : multipart.getBodyParts()) {
String bodyText = getTextFromPart(bodyPart);
if (bodyText != null) {
if (text.isEmpty() && bodyPart.isMimeType("text/plain")) {
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..5f6cfd38c 100644
--- a/src/com/fsck/k9/mail/internet/TextBodyBuilder.java
+++ b/src/com/fsck/k9/mail/internet/TextBodyBuilder.java
@@ -204,7 +204,10 @@ public class TextBodyBuilder {
return mQuotedTextHtml;
}
- public String textToHtmlFragment(String text) {
+ /**
+ * protected for unit-test purposes
+ */
+ protected 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 f2c002e05..9042f9d9b 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;
@@ -29,8 +26,11 @@ import java.security.Security;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.Deque;
+import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
@@ -64,6 +64,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;
@@ -116,7 +117,7 @@ public class ImapStore extends Store {
private static int FETCH_WINDOW_SIZE = 100;
- private Set mPermanentFlagsIndex = new HashSet();
+ private Set mPermanentFlagsIndex = EnumSet.noneOf(Flag.class);
private static final String CAPABILITY_IDLE = "IDLE";
private static final String CAPABILITY_AUTH_CRAM_MD5 = "AUTH=CRAM-MD5";
@@ -133,8 +134,6 @@ public class ImapStore extends Store {
private static final String CAPABILITY_COMPRESS_DEFLATE = "COMPRESS=DEFLATE";
private static final String COMMAND_COMPRESS_DEFLATE = "COMPRESS DEFLATE";
- private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0];
-
private static final String[] EMPTY_STRING_ARRAY = new String[0];
/**
@@ -198,31 +197,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 = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ } else if (userInfoParts.length == 2) {
+ authenticationType = AuthType.PLAIN;
+ username = UrlEncodingHelper.decodeUtf8(userInfoParts[0]);
+ password = UrlEncodingHelper.decodeUtf8(userInfoParts[1]);
+ } else if (userInfoParts.length == 3) {
+ authenticationType = AuthType.valueOf(userInfoParts[0]);
+ username = 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 = UrlEncodingHelper.decodeUtf8(userInfoParts[2]);
+ } else {
+ password = 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);
}
}
@@ -260,19 +254,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 = UrlEncodingHelper.encodeUtf8(server.username);
+ String passwordEnc = (server.password != null) ?
+ UrlEncodingHelper.encodeUtf8(server.password) : "";
+ String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
+ UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
@@ -439,7 +425,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 +439,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);
@@ -756,18 +742,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 {
@@ -775,18 +753,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
@@ -1121,22 +1092,22 @@ public class ImapStore extends Store {
* @return The mapping of original message UIDs to the new server UIDs.
*/
@Override
- public Map copyMessages(Message[] messages, Folder folder)
+ public Map copyMessages(List extends Message> messages, Folder folder)
throws MessagingException {
if (!(folder instanceof ImapFolder)) {
throw new MessagingException("ImapFolder.copyMessages passed non-ImapFolder");
}
- if (messages.length == 0) {
+ if (messages.isEmpty()) {
return null;
}
ImapFolder iFolder = (ImapFolder)folder;
checkOpen(); //only need READ access
- String[] uids = new String[messages.length];
- for (int i = 0, count = messages.length; i < count; i++) {
- uids[i] = messages[i].getUid();
+ String[] uids = new String[messages.size()];
+ for (int i = 0, count = messages.size(); i < count; i++) {
+ uids[i] = messages.get(i).getUid();
}
try {
@@ -1222,21 +1193,21 @@ public class ImapStore extends Store {
}
@Override
- public Map moveMessages(Message[] messages, Folder folder) throws MessagingException {
- if (messages.length == 0)
+ public Map moveMessages(List extends Message> messages, Folder folder) throws MessagingException {
+ if (messages.isEmpty())
return null;
Map uidMap = copyMessages(messages, folder);
- setFlags(messages, new Flag[] { Flag.DELETED }, true);
+ setFlags(messages, Collections.singleton(Flag.DELETED), true);
return uidMap;
}
@Override
- public void delete(Message[] messages, String trashFolderName) throws MessagingException {
- if (messages.length == 0)
+ public void delete(List extends Message> messages, String trashFolderName) throws MessagingException {
+ if (messages.isEmpty())
return;
if (trashFolderName == null || getName().equalsIgnoreCase(trashFolderName)) {
- setFlags(messages, new Flag[] { Flag.DELETED }, true);
+ setFlags(messages, Collections.singleton(Flag.DELETED), true);
} else {
ImapFolder remoteTrashFolder = (ImapFolder)getStore().getFolder(trashFolderName);
String remoteTrashName = encodeString(encodeFolderName(remoteTrashFolder.getPrefixedName()));
@@ -1252,7 +1223,7 @@ public class ImapStore extends Store {
if (exists(remoteTrashName)) {
if (K9.DEBUG)
- Log.d(K9.LOG_TAG, "IMAPMessage.delete: copying remote " + messages.length + " messages to '" + trashFolderName + "' for " + getLogId());
+ Log.d(K9.LOG_TAG, "IMAPMessage.delete: copying remote " + messages.size() + " messages to '" + trashFolderName + "' for " + getLogId());
moveMessages(messages, remoteTrashFolder);
} else {
@@ -1302,13 +1273,14 @@ public class ImapStore extends Store {
protected long getHighestUid() {
try {
ImapSearcher searcher = new ImapSearcher() {
+ @Override
public List search() throws IOException, MessagingException {
return executeSimpleCommand("UID SEARCH *:*");
}
};
- Message[] messages = search(searcher, null).toArray(EMPTY_MESSAGE_ARRAY);
- if (messages.length > 0) {
- return Long.parseLong(messages[0].getUid());
+ List extends Message> messages = search(searcher, null);
+ if (messages.size() > 0) {
+ return Long.parseLong(messages.get(0).getUid());
}
} catch (Exception e) {
Log.e(K9.LOG_TAG, "Unable to find highest UID in folder " + getName(), e);
@@ -1329,12 +1301,12 @@ public class ImapStore extends Store {
@Override
- public Message[] getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
+ public List extends Message> getMessages(int start, int end, Date earliestDate, MessageRetrievalListener listener)
throws MessagingException {
return getMessages(start, end, earliestDate, false, listener);
}
- protected Message[] getMessages(final int start, final int end, Date earliestDate, final boolean includeDeleted, final MessageRetrievalListener listener)
+ protected List extends Message> getMessages(final int start, final int end, Date earliestDate, final boolean includeDeleted, final MessageRetrievalListener listener)
throws MessagingException {
if (start < 1 || end < 1 || end < start) {
throw new MessagingException(
@@ -1351,39 +1323,42 @@ 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"));
}
};
- return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY);
+ return search(searcher, listener);
}
- protected Message[] getMessages(final List mesgSeqs, final boolean includeDeleted, final MessageRetrievalListener listener)
+ protected List extends 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"));
}
};
- return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY);
+ return search(searcher, listener);
}
- protected Message[] getMessagesFromUids(final List mesgUids, final boolean includeDeleted, final MessageRetrievalListener listener)
+ protected List extends 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"));
}
};
- return search(searcher, listener).toArray(EMPTY_MESSAGE_ARRAY);
+ return search(searcher, listener);
}
private List search(ImapSearcher searcher, MessageRetrievalListener listener) throws MessagingException {
checkOpen(); //only need READ access
- ArrayList messages = new ArrayList();
+ List messages = new ArrayList();
try {
- ArrayList uids = new ArrayList();
+ List uids = new ArrayList();
List responses = searcher.search(); //
for (ImapResponse response : responses) {
if (response.mTag == null) {
@@ -1420,19 +1395,19 @@ public class ImapStore extends Store {
@Override
- public Message[] getMessages(MessageRetrievalListener listener) throws MessagingException {
+ public List extends Message> getMessages(MessageRetrievalListener listener) throws MessagingException {
return getMessages(null, listener);
}
@Override
- public Message[] getMessages(String[] uids, MessageRetrievalListener listener)
+ public List extends Message> getMessages(String[] uids, MessageRetrievalListener listener)
throws MessagingException {
checkOpen(); //only need READ access
- ArrayList messages = new ArrayList();
+ List messages = new ArrayList();
try {
if (uids == null) {
List responses = executeSimpleCommand("UID SEARCH 1:* NOT DELETED");
- ArrayList tempUids = new ArrayList();
+ List tempUids = new ArrayList();
for (ImapResponse response : responses) {
if (ImapResponseParser.equalsIgnoreCase(response.get(0), "SEARCH")) {
for (int i = 1, count = response.size(); i < count; i++) {
@@ -1455,17 +1430,17 @@ public class ImapStore extends Store {
} catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe);
}
- return messages.toArray(EMPTY_MESSAGE_ARRAY);
+ return messages;
}
@Override
- public void fetch(Message[] messages, FetchProfile fp, MessageRetrievalListener listener)
+ public void fetch(List extends Message> messages, FetchProfile fp, MessageRetrievalListener listener)
throws MessagingException {
- if (messages == null || messages.length == 0) {
+ if (messages == null || messages.isEmpty()) {
return;
}
checkOpen(); //only need READ access
- List uids = new ArrayList(messages.length);
+ List uids = new ArrayList(messages.size());
HashMap messageMap = new HashMap();
for (Message msg : messages) {
String uid = msg.getUid();
@@ -1507,8 +1482,8 @@ public class ImapStore extends Store {
- for (int windowStart = 0; windowStart < messages.length; windowStart += (FETCH_WINDOW_SIZE)) {
- List uidWindow = uids.subList(windowStart, Math.min((windowStart + FETCH_WINDOW_SIZE), messages.length));
+ for (int windowStart = 0; windowStart < messages.size(); windowStart += (FETCH_WINDOW_SIZE)) {
+ List uidWindow = uids.subList(windowStart, Math.min((windowStart + FETCH_WINDOW_SIZE), messages.size()));
try {
mConnection.sendCommand(String.format("UID FETCH %s (%s)",
@@ -1834,7 +1809,7 @@ public class ImapStore extends Store {
* For each part in the message we're going to add a new BodyPart and parse
* into it.
*/
- ImapBodyPart bp = new ImapBodyPart();
+ MimeBodyPart bp = new MimeBodyPart();
if (id.equalsIgnoreCase("TEXT")) {
parseBodyStructure(bs.getList(i), bp, Integer.toString(i + 1));
} else {
@@ -1970,10 +1945,6 @@ public class ImapStore extends Store {
if (part instanceof ImapMessage) {
((ImapMessage) part).setSize(size);
- } else if (part instanceof ImapBodyPart) {
- ((ImapBodyPart) part).setSize(size);
- } else {
- throw new MessagingException("Unknown part type " + part.toString());
}
part.setHeader(MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA, id);
}
@@ -1994,7 +1965,7 @@ public class ImapStore extends Store {
* @return The mapping of original message UIDs to the new server UIDs.
*/
@Override
- public Map appendMessages(Message[] messages) throws MessagingException {
+ public Map appendMessages(List extends Message> messages) throws MessagingException {
open(OPEN_MODE_RW);
checkOpen();
try {
@@ -2066,7 +2037,7 @@ public class ImapStore extends Store {
* with the behavior of other similar methods (copyMessages, moveMessages) which
* return null.
*/
- return (uidMap.size() == 0) ? null : uidMap;
+ return (uidMap.isEmpty()) ? null : uidMap;
} catch (IOException ioe) {
throw ioExceptionHandler(mConnection, ioe);
}
@@ -2117,8 +2088,8 @@ public class ImapStore extends Store {
}
}
- private String combineFlags(Flag[] flags) {
- ArrayList flagNames = new ArrayList();
+ private String combineFlags(Iterable flags) {
+ List flagNames = new ArrayList();
for (Flag flag : flags) {
if (flag == Flag.SEEN) {
flagNames.add("\\Seen");
@@ -2139,7 +2110,7 @@ public class ImapStore extends Store {
@Override
- public void setFlags(Flag[] flags, boolean value)
+ public void setFlags(Set flags, boolean value)
throws MessagingException {
open(OPEN_MODE_RW);
checkOpen();
@@ -2174,13 +2145,13 @@ public class ImapStore extends Store {
@Override
- public void setFlags(Message[] messages, Flag[] flags, boolean value)
+ public void setFlags(List extends Message> messages, final Set flags, boolean value)
throws MessagingException {
open(OPEN_MODE_RW);
checkOpen();
- String[] uids = new String[messages.length];
- for (int i = 0, count = messages.length; i < count; i++) {
- uids[i] = messages[i].getUid();
+ String[] uids = new String[messages.size()];
+ for (int i = 0, count = messages.size(); i < count; i++) {
+ uids[i] = messages.get(i).getUid();
}
try {
executeSimpleCommand(String.format("UID STORE %s %sFLAGS.SILENT (%s)",
@@ -2241,7 +2212,7 @@ public class ImapStore extends Store {
* @throws MessagingException On any error.
*/
@Override
- public List search(final String queryString, final Flag[] requiredFlags, final Flag[] forbiddenFlags)
+ public List search(final String queryString, final Set requiredFlags, final Set forbiddenFlags)
throws MessagingException {
if (!mAccount.allowRemoteSearch()) {
@@ -2250,6 +2221,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) {
@@ -2345,12 +2317,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;
@@ -2761,10 +2733,10 @@ public class ImapStore extends Store {
return response;
}
- protected ArrayList readStatusResponse(String tag,
+ protected List readStatusResponse(String tag,
String commandToLog, UntaggedHandler untaggedHandler)
throws IOException, MessagingException {
- ArrayList responses = new ArrayList();
+ List responses = new ArrayList();
ImapResponse response;
do {
response = mParser.readResponse();
@@ -2931,11 +2903,6 @@ public class ImapStore extends Store {
this.mSize = size;
}
- @Override
- public void parse(InputStream in) throws IOException, MessagingException {
- super.parse(in);
- }
-
public void setFlagInternal(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set);
}
@@ -2944,28 +2911,18 @@ public class ImapStore extends Store {
@Override
public void setFlag(Flag flag, boolean set) throws MessagingException {
super.setFlag(flag, set);
- mFolder.setFlags(new Message[] { this }, new Flag[] { flag }, set);
+ mFolder.setFlags(Collections.singletonList(this), Collections.singleton(flag), set);
}
@Override
public void delete(String trashFolderName) throws MessagingException {
- getFolder().delete(new Message[] { this }, trashFolderName);
- }
- }
-
- static class ImapBodyPart extends MimeBodyPart {
- public ImapBodyPart() throws MessagingException {
- super();
- }
-
- public void setSize(int size) {
- this.mSize = size;
+ getFolder().delete(Collections.singletonList(this), trashFolderName);
}
}
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);
@@ -2976,22 +2933,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);
@@ -3029,6 +2983,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)
@@ -3254,9 +3209,9 @@ public class ImapStore extends Store {
Log.e(K9.LOG_TAG, "Unable to get oldUidNext for " + getLogId(), e);
}
- Message[] messageArray = getMessages(end, end, null, true, null);
- if (messageArray != null && messageArray.length > 0) {
- long newUid = Long.parseLong(messageArray[0].getUid());
+ List extends Message> messageList = getMessages(end, end, null, true, null);
+ if (messageList != null && messageList.size() > 0) {
+ long newUid = Long.parseLong(messageList.get(0).getUid());
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Got newUid " + newUid + " for message " + end + " on " + getLogId());
long startUid = oldUidNext;
@@ -3284,12 +3239,10 @@ public class ImapStore extends Store {
private void syncMessages(List flagSyncMsgSeqs) {
try {
- Message[] messageArray = null;
-
- messageArray = getMessages(flagSyncMsgSeqs, true, null);
+ List extends Message> messageList = getMessages(flagSyncMsgSeqs, true, null);
List messages = new ArrayList();
- messages.addAll(Arrays.asList(messageArray));
+ messages.addAll(messageList);
pushMessages(messages, false);
} catch (Exception e) {
@@ -3301,7 +3254,7 @@ public class ImapStore extends Store {
List messages = new ArrayList(removeUids.size());
try {
- Message[] existingMessages = getMessagesFromUids(removeUids, true, null);
+ List extends Message> existingMessages = getMessagesFromUids(removeUids, true, null);
for (Message existingMessage : existingMessages) {
needsPoll.set(true);
msgSeqUidMap.clear();
@@ -3428,6 +3381,7 @@ public class ImapStore extends Store {
}
}
+ @Override
public void handleAsyncUntaggedResponse(ImapResponse response) {
if (K9.DEBUG)
Log.v(K9.LOG_TAG, "Got async response: " + response);
@@ -3478,17 +3432,18 @@ public class ImapStore extends Store {
}
public class ImapPusher implements Pusher {
- final ImapStore mStore;
+ private final ImapStore mStore;
final PushReceiver mReceiver;
private long lastRefresh = -1;
- HashMap folderPushers = new HashMap();
+ final Map folderPushers = new HashMap();
public ImapPusher(ImapStore store, PushReceiver receiver) {
mStore = store;
mReceiver = receiver;
}
+ @Override
public void start(List folderNames) {
stop();
synchronized (folderPushers) {
@@ -3504,6 +3459,7 @@ public class ImapStore extends Store {
}
}
+ @Override
public void refresh() {
synchronized (folderPushers) {
for (ImapFolderPusher folderPusher : folderPushers.values()) {
@@ -3516,6 +3472,7 @@ public class ImapStore extends Store {
}
}
+ @Override
public void stop() {
if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Requested stop of IMAP pusher");
@@ -3534,14 +3491,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;
}
@@ -3590,9 +3550,9 @@ public class ImapStore extends Store {
}
private static class FetchBodyCallback implements ImapResponseParser.IImapResponseCallback {
- private HashMap mMessageMap;
+ private Map mMessageMap;
- FetchBodyCallback(HashMap mesageMap) {
+ FetchBodyCallback(Map mesageMap) {
mMessageMap = mesageMap;
}
diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java
deleted file mode 100644
index 8e1aca3eb..000000000
--- a/src/com/fsck/k9/mail/store/LocalStore.java
+++ /dev/null
@@ -1,4550 +0,0 @@
-
-package com.fsck.k9.mail.store;
-
-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;
-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.codec.QuotedPrintableOutputStream;
-import org.apache.james.mime4j.util.MimeUtil;
-
-import android.app.Application;
-import android.content.ContentResolver;
-import android.content.ContentValues;
-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.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.CompositeBody;
-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.filter.Base64OutputStream;
-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.DbCallback;
-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;
-import com.fsck.k9.search.SearchSpecification.Attribute;
-import com.fsck.k9.search.SearchSpecification.Searchfield;
-import com.fsck.k9.search.SqlQueryBuilder;
-
-/**
- *
- * Implements a SQLite database backed local store for Messages.
- *
- */
-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];
- private 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 =
- "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 =
- "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;
-
- private 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;
-
- /**
- * Maximum number of messages to perform flag updates on at once.
- *
- * @see #setFlag(List, Flag, boolean, boolean)
- */
- private static final int FLAG_UPDATE_BATCH_SIZE = 500;
-
- /**
- * Maximum number of threads to perform flag updates on at once.
- *
- * @see #setFlagForThreads(List, Flag, boolean)
- */
- private static final int THREAD_FLAG_UPDATE_BATCH_SIZE = 500;
-
- public static final int DB_VERSION = 50;
-
-
- public static String getColumnNameForFlag(Flag flag) {
- switch (flag) {
- case SEEN: {
- return MessageColumns.READ;
- }
- case FLAGGED: {
- return MessageColumns.FLAGGED;
- }
- case ANSWERED: {
- return MessageColumns.ANSWERED;
- }
- case FORWARDED: {
- return MessageColumns.FORWARDED;
- }
- default: {
- throw new IllegalArgumentException("Flag must be a special column flag");
- }
- }
- }
-
-
- protected String uUid = null;
-
- private final Application mApplication;
-
- private LockableDatabase database;
-
- private ContentResolver mContentResolver;
-
- /**
- * local://localhost/path/to/database/uuid.db
- * This constructor is only used by {@link Store#getLocalInstance(Account, Application)}
- * @param account
- * @param application
- * @throws UnavailableStorageException if not {@link StorageProvider#isReady(Context)}
- */
- public LocalStore(final Account account, final Application application) throws MessagingException {
- super(account);
- database = new LockableDatabase(application, account.getUuid(), new StoreSchemaDefinition());
-
- mApplication = application;
- mContentResolver = application.getContentResolver();
- database.setStorageProviderId(account.getLocalStorageProviderId());
- uUid = account.getUuid();
-
- database.open();
- }
-
- public void switchLocalStorage(final String newStorageProviderId) throws MessagingException {
- database.switchProvider(newStorageProviderId);
- }
-
- protected SharedPreferences getPreferences() {
- 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("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(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(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);
-
- final File attachmentDirectory = storageManager.getAttachmentDirectory(uUid,
- database.getStorageProviderId());
-
- return database.execute(false, new DbCallback() {
- @Override
- public Long doDbWork(final SQLiteDatabase db) {
- final File[] files = attachmentDirectory.listFiles();
- long attachmentLength = 0;
- if (files != null) {
- for (File file : files) {
- if (file.exists()) {
- attachmentLength += file.length();
- }
- }
- }
-
- final File dbFile = storageManager.getDatabase(uUid, database.getStorageProviderId());
- return dbFile.length() + attachmentLength;
- }
- });
- }
-
- public void compact() throws MessagingException {
- if (K9.DEBUG)
- Log.i(K9.LOG_TAG, "Before compaction size = " + getSize());
-
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- db.execSQL("VACUUM");
- return null;
- }
- });
- if (K9.DEBUG)
- Log.i(K9.LOG_TAG, "After compaction size = " + getSize());
- }
-
-
- public void clear() throws MessagingException {
- if (K9.DEBUG)
- Log.i(K9.LOG_TAG, "Before prune size = " + getSize());
-
- pruneCachedAttachments(true);
- if (K9.DEBUG) {
- Log.i(K9.LOG_TAG, "After prune / before compaction size = " + getSize());
-
- Log.i(K9.LOG_TAG, "Before clear folder count = " + getFolderCount());
- Log.i(K9.LOG_TAG, "Before clear message count = " + getMessageCount());
-
- Log.i(K9.LOG_TAG, "After prune / before clear size = " + getSize());
- }
- // don't delete messages that are Local, since there is no copy on the server.
- // Don't delete deleted messages. They are essentially placeholders for UIDs of messages that have
- // been deleted locally. They take up insignificant space
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) {
- // Delete entries from 'threads' table
- db.execSQL("DELETE FROM threads WHERE message_id IN " +
- "(SELECT id FROM messages WHERE deleted = 0 AND uid NOT LIKE 'Local%')");
-
- // Set 'root' and 'parent' of remaining entries in 'thread' table to 'NULL' to make
- // sure the thread structure is in a valid state (this may destroy existing valid
- // thread trees, but is much faster than adjusting the tree by removing messages
- // one by one).
- db.execSQL("UPDATE threads SET root=id, parent=NULL");
-
- // Delete entries from 'messages' table
- db.execSQL("DELETE FROM messages WHERE deleted = 0 AND uid NOT LIKE 'Local%'");
- return null;
- }
- });
-
- compact();
-
- if (K9.DEBUG) {
- Log.i(K9.LOG_TAG, "After clear message count = " + getMessageCount());
-
- Log.i(K9.LOG_TAG, "After clear size = " + getSize());
- }
- }
-
- public int getMessageCount() throws MessagingException {
- return database.execute(false, new DbCallback() {
- @Override
- public Integer doDbWork(final SQLiteDatabase db) {
- Cursor cursor = null;
- try {
- cursor = db.rawQuery("SELECT COUNT(*) FROM messages", null);
- cursor.moveToFirst();
- return cursor.getInt(0); // message count
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
- });
- }
-
- public int getFolderCount() throws MessagingException {
- return database.execute(false, new DbCallback() {
- @Override
- public Integer doDbWork(final SQLiteDatabase db) {
- Cursor cursor = null;
- try {
- cursor = db.rawQuery("SELECT COUNT(*) FROM folders", null);
- cursor.moveToFirst();
- return cursor.getInt(0); // folder count
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
- });
- }
-
- @Override
- public LocalFolder getFolder(String name) {
- return new LocalFolder(name);
- }
-
- public LocalFolder getFolderById(long folderId) {
- return new LocalFolder(folderId);
- }
-
- // TODO this takes about 260-300ms, seems slow.
- @Override
- public List extends Folder > getPersonalNamespaces(boolean forceListAll) throws MessagingException {
- final List folders = new LinkedList();
- try {
- database.execute(false, new DbCallback < List extends Folder >> () {
- @Override
- public List extends Folder > doDbWork(final SQLiteDatabase db) throws WrappedException {
- Cursor cursor = null;
-
- try {
- cursor = db.rawQuery("SELECT " + GET_FOLDER_COLS + " FROM folders " +
- "ORDER BY name ASC", null);
- while (cursor.moveToNext()) {
- if (cursor.isNull(FOLDER_ID_INDEX)) {
- continue;
- }
- String folderName = cursor.getString(FOLDER_NAME_INDEX);
- LocalFolder folder = new LocalFolder(folderName);
- folder.open(cursor);
-
- folders.add(folder);
- }
- return folders;
- } catch (MessagingException e) {
- throw new WrappedException(e);
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
- });
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
- return folders;
- }
-
- @Override
- public void checkSettings() throws MessagingException {
- }
-
- public void delete() throws UnavailableStorageException {
- database.delete();
- }
-
- public void recreate() throws UnavailableStorageException {
- 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
- */
- private void pruneCachedAttachments(final boolean force) throws MessagingException {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- if (force) {
- ContentValues cv = new ContentValues();
- cv.putNull("content_uri");
- db.update("attachments", cv, null, null);
- }
- final StorageManager storageManager = StorageManager.getInstance(mApplication);
- File[] files = storageManager.getAttachmentDirectory(uUid, database.getStorageProviderId()).listFiles();
- for (File file : files) {
- if (file.exists()) {
- if (!force) {
- Cursor cursor = null;
- try {
- cursor = db.query(
- "attachments",
- new String[] { "store_data" },
- "id = ?",
- new String[] { file.getName() },
- null,
- null,
- null);
- if (cursor.moveToNext()) {
- if (cursor.getString(0) == null) {
- if (K9.DEBUG)
- Log.d(K9.LOG_TAG, "Attachment " + file.getAbsolutePath() + " has no store data, not deleting");
- /*
- * If the attachment has no store data it is not recoverable, so
- * we won't delete it.
- */
- continue;
- }
- }
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
- if (!force) {
- try {
- ContentValues cv = new ContentValues();
- cv.putNull("content_uri");
- db.update("attachments", cv, "id = ?", new String[] { file.getName() });
- } catch (Exception e) {
- /*
- * If the row has gone away before we got to mark it not-downloaded that's
- * okay.
- */
- }
- }
- if (!file.delete()) {
- file.deleteOnExit();
- }
- }
- }
- return null;
- }
- });
- }
-
- 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));
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- db.update("folders", cv, null, null);
- return null;
- }
- });
- }
-
- public ArrayList getPendingCommands() throws UnavailableStorageException {
- return database.execute(false, new DbCallback>() {
- @Override
- public ArrayList doDbWork(final SQLiteDatabase db) throws WrappedException {
- Cursor cursor = null;
- try {
- cursor = db.query("pending_commands",
- new String[] { "id", "command", "arguments" },
- null,
- null,
- null,
- null,
- "id ASC");
- ArrayList commands = new ArrayList();
- while (cursor.moveToNext()) {
- PendingCommand command = new PendingCommand();
- command.mId = cursor.getLong(0);
- command.command = cursor.getString(1);
- String arguments = cursor.getString(2);
- command.arguments = arguments.split(",");
- for (int i = 0; i < command.arguments.length; i++) {
- command.arguments[i] = Utility.fastUrlDecode(command.arguments[i]);
- }
- commands.add(command);
- }
- return commands;
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
- });
- }
-
- 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.");
- }
- }
-
- public void removePendingCommand(final PendingCommand command) throws UnavailableStorageException {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- db.delete("pending_commands", "id = ?", new String[] { Long.toString(command.mId) });
- return null;
- }
- });
- }
-
- public void removePendingCommands() throws UnavailableStorageException {
- database.execute(false, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- db.delete("pending_commands", null, null);
- return null;
- }
- });
- }
-
- public static class PendingCommand {
- private long mId;
- public String command;
- public String[] arguments;
-
- @Override
- public String toString() {
- StringBuilder sb = new StringBuilder();
- sb.append(command);
- sb.append(": ");
- for (String argument : arguments) {
- sb.append(", ");
- sb.append(argument);
- //sb.append("\n");
- }
- return sb.toString();
- }
- }
-
- @Override
- public boolean isMoveCapable() {
- return true;
- }
-
- @Override
- public boolean isCopyCapable() {
- return true;
- }
-
- public Message[] searchForMessages(MessageRetrievalListener retrievalListener,
- LocalSearch search) throws MessagingException {
-
- StringBuilder query = new StringBuilder();
- List queryArgs = new ArrayList();
- SqlQueryBuilder.buildWhereClause(mAccount, search.getConditions(), query, queryArgs);
-
- // Avoid "ambiguous column name" error by prefixing "id" with the message table name
- String where = SqlQueryBuilder.addPrefixToSelection(new String[] { "id" },
- "messages.", query.toString());
-
- String[] selectionArgs = queryArgs.toArray(EMPTY_STRING_ARRAY);
-
- String sqlQuery = "SELECT " + GET_MESSAGES_COLS + "FROM messages " +
- "LEFT JOIN threads ON (threads.message_id = messages.id) " +
- "LEFT JOIN folders ON (folders.id = messages.folder_id) WHERE " +
- "((empty IS NULL OR empty != 1) AND deleted = 0)" +
- ((!StringUtils.isNullOrEmpty(where)) ? " AND (" + where + ")" : "") +
- " ORDER BY date DESC";
-
- if (K9.DEBUG) {
- Log.d(K9.LOG_TAG, "Query = " + sqlQuery);
- }
-
- return getMessages(retrievalListener, null, sqlQuery, selectionArgs);
- }
-
- /*
- * Given a query string, actually do the query for the messages and
- * call the MessageRetrievalListener for each one
- */
- private Message[] getMessages(
- final MessageRetrievalListener listener,
- final LocalFolder folder,
- final String queryString, final String[] placeHolders
- ) throws MessagingException {
- final ArrayList messages = new ArrayList();
- final int j = database.execute(false, new DbCallback() {
- @Override
- public Integer doDbWork(final SQLiteDatabase db) throws WrappedException {
- Cursor cursor = null;
- int i = 0;
- try {
- cursor = db.rawQuery(queryString + " LIMIT 10", placeHolders);
-
- while (cursor.moveToNext()) {
- LocalMessage message = new LocalMessage(null, folder);
- message.populateFromGetMessageCursor(cursor);
-
- messages.add(message);
- if (listener != null) {
- listener.messageFinished(message, i, -1);
- }
- i++;
- }
- cursor.close();
- cursor = db.rawQuery(queryString + " LIMIT -1 OFFSET 10", placeHolders);
-
- while (cursor.moveToNext()) {
- LocalMessage message = new LocalMessage(null, folder);
- message.populateFromGetMessageCursor(cursor);
-
- messages.add(message);
- if (listener != null) {
- listener.messageFinished(message, i, -1);
- }
- i++;
- }
- } catch (Exception e) {
- Log.d(K9.LOG_TAG, "Got an exception", e);
- } finally {
- Utility.closeQuietly(cursor);
- }
- return i;
- }
- });
- if (listener != null) {
- listener.messagesFinished(j);
- }
-
- return messages.toArray(EMPTY_MESSAGE_ARRAY);
-
- }
-
- public Message[] getMessagesInThread(final long rootId) throws MessagingException {
- String rootIdString = Long.toString(rootId);
-
- LocalSearch search = new LocalSearch();
- search.and(Searchfield.THREAD_ID, rootIdString, Attribute.EQUALS);
-
- return searchForMessages(null, search);
- }
-
- public AttachmentInfo getAttachmentInfo(final String attachmentId) throws UnavailableStorageException {
- return database.execute(false, new DbCallback() {
- @Override
- public AttachmentInfo doDbWork(final SQLiteDatabase db) throws WrappedException {
- String name;
- String type;
- int size;
- Cursor cursor = null;
- try {
- cursor = db.query(
- "attachments",
- new String[] { "name", "size", "mime_type" },
- "id = ?",
- new String[] { attachmentId },
- null,
- null,
- null);
- if (!cursor.moveToFirst()) {
- return null;
- }
- name = cursor.getString(0);
- size = cursor.getInt(1);
- type = cursor.getString(2);
- final AttachmentInfo attachmentInfo = new AttachmentInfo();
- attachmentInfo.name = name;
- attachmentInfo.size = size;
- attachmentInfo.type = type;
- return attachmentInfo;
- } finally {
- Utility.closeQuietly(cursor);
- }
- }
- });
- }
-
- public static class AttachmentInfo {
- public String name;
- public int size;
- public String type;
- }
-
- public void createFolders(final List foldersToCreate, final int visibleLimit) throws UnavailableStorageException {
- database.execute(true, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
- for (LocalFolder folder : foldersToCreate) {
- String name = folder.getName();
- final LocalFolder.PreferencesHolder prefHolder = folder.new PreferencesHolder();
-
- // When created, special folders should always be displayed
- // inbox should be integrated
- // and the inbox and drafts folders should be syncced by default
- if (mAccount.isSpecialFolder(name)) {
- prefHolder.inTopGroup = true;
- prefHolder.displayClass = LocalFolder.FolderClass.FIRST_CLASS;
- if (name.equalsIgnoreCase(mAccount.getInboxFolderName())) {
- prefHolder.integrate = true;
- prefHolder.notifyClass = LocalFolder.FolderClass.FIRST_CLASS;
- prefHolder.pushClass = LocalFolder.FolderClass.FIRST_CLASS;
- } else {
- prefHolder.pushClass = LocalFolder.FolderClass.INHERITED;
-
- }
- if (name.equalsIgnoreCase(mAccount.getInboxFolderName()) ||
- name.equalsIgnoreCase(mAccount.getDraftsFolderName())) {
- prefHolder.syncClass = LocalFolder.FolderClass.FIRST_CLASS;
- } else {
- prefHolder.syncClass = LocalFolder.FolderClass.NO_CLASS;
- }
- }
- folder.refresh(name, prefHolder); // Recover settings from Preferences
-
- db.execSQL("INSERT INTO folders (name, visible_limit, top_group, display_class, poll_class, notify_class, push_class, integrate) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", new Object[] {
- name,
- visibleLimit,
- prefHolder.inTopGroup ? 1 : 0,
- prefHolder.displayClass.name(),
- prefHolder.syncClass.name(),
- prefHolder.notifyClass.name(),
- prefHolder.pushClass.name(),
- prefHolder.integrate ? 1 : 0,
- });
-
- }
- return null;
- }
- });
- }
-
-
- private String serializeFlags(Flag[] flags) {
- List extraFlags = new ArrayList();
-
- for (Flag flag : flags) {
- switch (flag) {
- case DELETED:
- case SEEN:
- case FLAGGED:
- case ANSWERED:
- case FORWARDED: {
- break;
- }
- default: {
- extraFlags.add(flag);
- }
- }
- }
-
- 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 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;
- 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 LocalStore.LocalTextBody) {
- text = ((LocalStore.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;
- }
- }
-
- 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;
- }
- }
-
- 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;
- 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() {
- Uri uri = Uri.withAppendedPath(EmailProvider.CONTENT_URI, "account/" + uUid + "/messages");
- mContentResolver.notifyChange(uri, null);
- }
-
- /**
- * Split database operations with a large set of arguments into multiple SQL statements.
- *
- *
- * At the time of this writing (2012-12-06) SQLite only supports around 1000 arguments. That's
- * why we have to split SQL statements with a large set of arguments into multiple SQL
- * statements each working on a subset of the arguments.
- *
- *
- * @param selectionCallback
- * Supplies the argument set and the code to query/update the database.
- * @param batchSize
- * The maximum size of the selection set in each SQL statement.
- *
- * @throws MessagingException
- */
- public void doBatchSetSelection(final BatchSetSelection selectionCallback, final int batchSize)
- throws MessagingException {
-
- final List selectionArgs = new ArrayList();
- int start = 0;
-
- while (start < selectionCallback.getListSize()) {
- final StringBuilder selection = new StringBuilder();
-
- selection.append(" IN (");
-
- int count = Math.min(selectionCallback.getListSize() - start, batchSize);
-
- for (int i = start, end = start + count; i < end; i++) {
- if (i > start) {
- selection.append(",?");
- } else {
- selection.append("?");
- }
-
- selectionArgs.add(selectionCallback.getListItem(i));
- }
-
- selection.append(")");
-
- try {
- database.execute(true, new DbCallback() {
- @Override
- public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
- UnavailableStorageException {
-
- selectionCallback.doDbWork(db, selection.toString(),
- selectionArgs.toArray(EMPTY_STRING_ARRAY));
-
- return null;
- }
- });
-
- selectionCallback.postDbWork();
-
- } catch (WrappedException e) {
- throw(MessagingException) e.getCause();
- }
-
- selectionArgs.clear();
- start += count;
- }
- }
-
- /**
- * Defines the behavior of {@link LocalStore#doBatchSetSelection(BatchSetSelection, int)}.
- */
- public interface BatchSetSelection {
- /**
- * @return The size of the argument list.
- */
- int getListSize();
-
- /**
- * Get a specific item of the argument list.
- *
- * @param index
- * The index of the item.
- *
- * @return Item at position {@code i} of the argument list.
- */
- String getListItem(int index);
-
- /**
- * Execute the SQL statement.
- *
- * @param db
- * Use this {@link SQLiteDatabase} instance for your SQL statement.
- * @param selectionSet
- * A partial selection string containing place holders for the argument list, e.g.
- * {@code " IN (?,?,?)"} (starts with a space).
- * @param selectionArgs
- * The current subset of the argument list.
- * @throws UnavailableStorageException
- */
- void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
- throws UnavailableStorageException;
-
- /**
- * This will be executed after each invocation of
- * {@link #doDbWork(SQLiteDatabase, String, String[])} (after the transaction has been
- * committed).
- */
- void postDbWork();
- }
-
- /**
- * Change the state of a flag for a list of messages.
- *
- *
- * The goal of this method is to be fast. Currently this means using as few SQL UPDATE
- * statements as possible.
- *
- * @param messageIds
- * A list of primary keys in the "messages" table.
- * @param flag
- * The flag to change. This must be a flag with a separate column in the database.
- * @param newState
- * {@code true}, if the flag should be set. {@code false}, otherwise.
- *
- * @throws MessagingException
- */
- public void setFlag(final List messageIds, final Flag flag, final boolean newState)
- throws MessagingException {
-
- final ContentValues cv = new ContentValues();
- cv.put(getColumnNameForFlag(flag), newState);
-
- doBatchSetSelection(new BatchSetSelection() {
-
- @Override
- public int getListSize() {
- return messageIds.size();
- }
-
- @Override
- public String getListItem(int index) {
- return Long.toString(messageIds.get(index));
- }
-
- @Override
- public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
- throws UnavailableStorageException {
-
- db.update("messages", cv, "(empty IS NULL OR empty != 1) AND id" + selectionSet,
- selectionArgs);
- }
-
- @Override
- public void postDbWork() {
- notifyChange();
- }
- }, FLAG_UPDATE_BATCH_SIZE);
- }
-
- /**
- * Change the state of a flag for a list of threads.
- *
- *
- * The goal of this method is to be fast. Currently this means using as few SQL UPDATE
- * statements as possible.
- *
- * @param threadRootIds
- * A list of root thread IDs.
- * @param flag
- * The flag to change. This must be a flag with a separate column in the database.
- * @param newState
- * {@code true}, if the flag should be set. {@code false}, otherwise.
- *
- * @throws MessagingException
- */
- public void setFlagForThreads(final List threadRootIds, Flag flag, final boolean newState)
- throws MessagingException {
-
- final String flagColumn = getColumnNameForFlag(flag);
-
- doBatchSetSelection(new BatchSetSelection() {
-
- @Override
- public int getListSize() {
- return threadRootIds.size();
- }
-
- @Override
- public String getListItem(int index) {
- return Long.toString(threadRootIds.get(index));
- }
-
- @Override
- public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
- throws UnavailableStorageException {
-
- db.execSQL("UPDATE messages SET " + flagColumn + " = " + ((newState) ? "1" : "0") +
- " WHERE id IN (" +
- "SELECT m.id FROM threads t " +
- "LEFT JOIN messages m ON (t.message_id = m.id) " +
- "WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
- "AND t.root" + selectionSet + ")",
- selectionArgs);
- }
-
- @Override
- public void postDbWork() {
- notifyChange();
- }
- }, THREAD_FLAG_UPDATE_BATCH_SIZE);
- }
-
- /**
- * Get folder name and UID for the supplied messages.
- *
- * @param messageIds
- * A list of primary keys in the "messages" table.
- * @param threadedList
- * If this is {@code true}, {@code messageIds} contains the thread IDs of the messages
- * at the root of a thread. In that case return UIDs for all messages in these threads.
- * If this is {@code false} only the UIDs for messages in {@code messageIds} are
- * returned.
- *
- * @return The list of UIDs for the messages grouped by folder name.
- *
- * @throws MessagingException
- */
- public Map> getFoldersAndUids(final List messageIds,
- final boolean threadedList) throws MessagingException {
-
- final Map> folderMap = new HashMap>();
-
- doBatchSetSelection(new BatchSetSelection() {
-
- @Override
- public int getListSize() {
- return messageIds.size();
- }
-
- @Override
- public String getListItem(int index) {
- return Long.toString(messageIds.get(index));
- }
-
- @Override
- public void doDbWork(SQLiteDatabase db, String selectionSet, String[] selectionArgs)
- throws UnavailableStorageException {
-
- if (threadedList) {
- String sql = "SELECT m.uid, f.name " +
- "FROM threads t " +
- "LEFT JOIN messages m ON (t.message_id = m.id) " +
- "LEFT JOIN folders f ON (m.folder_id = f.id) " +
- "WHERE (m.empty IS NULL OR m.empty != 1) AND m.deleted = 0 " +
- "AND t.root" + selectionSet;
-
- getDataFromCursor(db.rawQuery(sql, selectionArgs));
-
- } else {
- String sql =
- "SELECT m.uid, f.name " +
- "FROM messages m " +
- "LEFT JOIN folders f ON (m.folder_id = f.id) " +
- "WHERE (m.empty IS NULL OR m.empty != 1) AND m.id" + selectionSet;
-
- getDataFromCursor(db.rawQuery(sql, selectionArgs));
- }
- }
-
- private void getDataFromCursor(Cursor cursor) {
- try {
- while (cursor.moveToNext()) {
- String uid = cursor.getString(0);
- String folderName = cursor.getString(1);
-
- List uidList = folderMap.get(folderName);
- if (uidList == null) {
- uidList = new ArrayList();
- folderMap.put(folderName, uidList);
- }
-
- uidList.add(uid);
- }
- } finally {
- cursor.close();
- }
- }
-
- @Override
- public void postDbWork() {
- notifyChange();
-
- }
- }, UID_CHECK_BATCH_SIZE);
-
- return folderMap;
- }
-}
diff --git a/src/com/fsck/k9/mail/store/LockableDatabase.java b/src/com/fsck/k9/mail/store/LockableDatabase.java
index 81138d6e8..f08076087 100644
--- a/src/com/fsck/k9/mail/store/LockableDatabase.java
+++ b/src/com/fsck/k9/mail/store/LockableDatabase.java
@@ -99,7 +99,7 @@ public class LockableDatabase {
}
try {
- openOrCreateDataspace(mApplication);
+ openOrCreateDataspace();
} catch (UnavailableStorageException e) {
Log.e(K9.LOG_TAG, "Unable to open DB on mount", e);
}
@@ -346,7 +346,7 @@ public class LockableDatabase {
mStorageProviderId = newProviderId;
// re-initialize this class with the new Uri
- openOrCreateDataspace(mApplication);
+ openOrCreateDataspace();
} finally {
unlockWrite(newProviderId);
}
@@ -358,7 +358,7 @@ public class LockableDatabase {
public void open() throws UnavailableStorageException {
lockWrite();
try {
- openOrCreateDataspace(mApplication);
+ openOrCreateDataspace();
} finally {
unlockWrite();
}
@@ -367,33 +367,20 @@ public class LockableDatabase {
/**
*
- * @param application
* @throws UnavailableStorageException
*/
- protected void openOrCreateDataspace(final Application application) throws UnavailableStorageException {
+ private void openOrCreateDataspace() throws UnavailableStorageException {
lockWrite();
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(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(databaseFile);
}
if (mDb.getVersion() != mSchemaDefinition.getVersion()) {
mSchemaDefinition.doDbUpgrade(mDb);
@@ -403,6 +390,17 @@ public class LockableDatabase {
}
}
+ private void doOpenOrCreateDb(final File databaseFile) {
+ if (StorageManager.InternalStorageProvider.ID.equals(mStorageProviderId)) {
+ // internal storage
+ mDb = mApplication.openOrCreateDatabase(databaseFile.getName(), Context.MODE_PRIVATE,
+ null);
+ } else {
+ // external storage
+ mDb = SQLiteDatabase.openOrCreateDatabase(databaseFile, null);
+ }
+ }
+
/**
* @param providerId
* Never null.
@@ -412,10 +410,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 +424,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 +460,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 +476,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));
@@ -490,7 +486,7 @@ public class LockableDatabase {
}
if (recreate) {
- openOrCreateDataspace(mApplication);
+ openOrCreateDataspace();
} else {
// stop waiting for mount/unmount events
getStorageManager().removeListener(mStorageListener);
diff --git a/src/com/fsck/k9/mail/store/Pop3Store.java b/src/com/fsck/k9/mail/store/Pop3Store.java
index 37c9b8efe..5e2ee632a 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;
@@ -23,6 +24,8 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.HashMap;
@@ -115,28 +118,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 = UrlEncodingHelper.decodeUtf8(userInfoParts[userIndex]);
+ if (userInfoParts.length > passwordIndex) {
+ if (authType == AuthType.EXTERNAL) {
+ clientCertificateAlias = UrlEncodingHelper.decodeUtf8(userInfoParts[passwordIndex]);
+ } else {
+ password = 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 +154,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 = UrlEncodingHelper.encodeUtf8(server.username);
+ String passwordEnc = (server.password != null) ?
+ UrlEncodingHelper.encodeUtf8(server.password) : "";
+ String clientCertificateAliasEnc = (server.clientCertificateAlias != null) ?
+ UrlEncodingHelper.encodeUtf8(server.clientCertificateAlias) : "";
String scheme;
switch (server.connectionSecurity) {
@@ -208,7 +198,7 @@ public class Pop3Store extends Store {
private String mClientCertificateAlias;
private AuthType mAuthType;
private ConnectionSecurity mConnectionSecurity;
- private HashMap mFolders = new HashMap();
+ private Map mFolders = new HashMap();
private Pop3Capabilities mCapabilities;
/**
@@ -286,9 +276,9 @@ public class Pop3Store extends Store {
private Socket mSocket;
private InputStream mIn;
private OutputStream mOut;
- private HashMap mUidToMsgMap = new HashMap();
- private HashMap mMsgNumToMsgMap = new HashMap();
- private HashMap mUidToMsgNumMap = new HashMap();
+ private Map mUidToMsgMap = new HashMap