From d13aa61de392f5d269676200c1467ada1dbb420d Mon Sep 17 00:00:00 2001 From: ashley willis Date: Sun, 5 Feb 2012 02:15:54 -0600 Subject: [PATCH] Added "advanced options" option and more support for local-only messages and folders: added message upload support. added advanced option to create local-only folder. disabled sync & push for local-only folders. hid expunge, check_mail, and "Load up to x more" for local-only folders. hid create/rename/delete folder options for webdav (someone give me a webdav account). added advanced option to change if a folder is local-only or syncable. add dialog to confirm clearing local messages, and if it should include local-only messages or just synced messages. added advanced option to automatically upload on move from local-only to syncable. changed background color in message list and subject color in message view of local-only messages. changed: keep local-only messages in folders no longer on server instead of deleting folder, and mark folder as local-only. added automatically fully downloading messages moved/copied to local-only folder. added advanced options option (if not enabled, those settings will be hidden). reworked onCreateFolder(), onRenameFolder(), onDeleteFolder(). added Store.isAppendCapable(). added LocalFolder.expunge() [unused]. --- res/layout/clear_local_folder.xml | 30 + res/layout/create_folder.xml | 21 + res/menu/message_list_context.xml | 4 + res/menu/message_list_option.xml | 4 + res/menu/message_view_option.xml | 12 +- res/values/strings.xml | 16 + res/xml/account_settings_preferences.xml | 6 + res/xml/folder_settings_preferences.xml | 6 + res/xml/global_preferences.xml | 5 + src/com/fsck/k9/Account.java | 14 + src/com/fsck/k9/K9.java | 14 +- src/com/fsck/k9/activity/ChooseFolder.java | 44 +- src/com/fsck/k9/activity/FolderList.java | 158 +++-- src/com/fsck/k9/activity/MessageList.java | 93 ++- src/com/fsck/k9/activity/MessageView.java | 27 + .../k9/activity/setup/AccountSettings.java | 12 + .../k9/activity/setup/FolderSettings.java | 58 +- src/com/fsck/k9/activity/setup/Prefs.java | 6 + .../k9/controller/MessagingController.java | 549 ++++++++++++------ src/com/fsck/k9/mail/Folder.java | 6 +- src/com/fsck/k9/mail/Store.java | 4 + src/com/fsck/k9/mail/store/ImapStore.java | 4 + src/com/fsck/k9/mail/store/LocalStore.java | 31 +- src/com/fsck/k9/mail/store/WebDavStore.java | 5 + .../fsck/k9/preferences/GlobalSettings.java | 3 + src/com/fsck/k9/view/MessageHeader.java | 8 +- 26 files changed, 866 insertions(+), 274 deletions(-) create mode 100644 res/layout/clear_local_folder.xml create mode 100644 res/layout/create_folder.xml diff --git a/res/layout/clear_local_folder.xml b/res/layout/clear_local_folder.xml new file mode 100644 index 000000000..8dba5c01d --- /dev/null +++ b/res/layout/clear_local_folder.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/res/layout/create_folder.xml b/res/layout/create_folder.xml new file mode 100644 index 000000000..b6ba01721 --- /dev/null +++ b/res/layout/create_folder.xml @@ -0,0 +1,21 @@ + + + + + diff --git a/res/menu/message_list_context.xml b/res/menu/message_list_context.xml index 31f63f0c9..1cebb0ebe 100644 --- a/res/menu/message_list_context.xml +++ b/res/menu/message_list_context.xml @@ -57,6 +57,10 @@ android:id="@+id/copy" android:title="@string/copy_action" /> + + - + + diff --git a/res/values/strings.xml b/res/values/strings.xml index b7f21d4dd..a39cd7129 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -76,6 +76,8 @@ Create folder Rename folder Delete folder + New folder name + Local-only Mark all messages as read Add account Compose @@ -124,6 +126,9 @@ Empty Trash Expunge Clear local messages + Clear local copies of remote messages? + Clear local-only messages? These will be permanently lost! + Include local-only messages? These will be permanently lost! Choose sort Reverse sort About @@ -310,6 +315,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin (Push) More from this sender + Upload Message copied Message moved @@ -421,6 +427,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Download headers Save all message headers locally + Upload on move or copy + Automatically upload local-only message when moved/copied to a syncable folder + External storage (SD card) Regular internal storage %1$s additional internal storage @@ -681,6 +690,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Show in top group Show near the top of the folder list + Local-only folder + Set to local-only folder (never synced with server) + Folder display class None 1st Class @@ -896,6 +908,7 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Move selected to Spam Move selected Copy selected + Upload selected Star mode Select mode Plain mode @@ -919,6 +932,9 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin Compact layouts Adjust layouts to display more on each page + Advanced options + Show advanced options + Volume key navigation Flip through items using the volume controls Message view diff --git a/res/xml/account_settings_preferences.xml b/res/xml/account_settings_preferences.xml index a852549f5..9996f4808 100644 --- a/res/xml/account_settings_preferences.xml +++ b/res/xml/account_settings_preferences.xml @@ -179,6 +179,12 @@ android:key="account_save_all_headers" android:summary="@string/account_setup_incoming_save_all_headers_label" /> + + + + diff --git a/res/xml/global_preferences.xml b/res/xml/global_preferences.xml index 58c41afdf..9551908a6 100644 --- a/res/xml/global_preferences.xml +++ b/res/xml/global_preferences.xml @@ -73,6 +73,11 @@ android:title="@string/compact_layouts_title" android:summary="@string/compact_layouts_summary" /> + diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index 3d49694b6..f956390fb 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -68,6 +68,7 @@ public class Account implements BaseAccount { public static final boolean DEFAULT_QUOTED_TEXT_SHOWN = true; public static final boolean DEFAULT_REPLY_AFTER_QUOTE = false; public static final boolean DEFAULT_STRIP_SIGNATURE = true; + public static final boolean DEFAULT_SHOW_ADVANCED_OPTIONS = false; public static final String ACCOUNT_DESCRIPTION_KEY = "description"; public static final String STORE_URI_KEY = "storeUri"; @@ -119,6 +120,7 @@ public class Account implements BaseAccount { private FolderMode mFolderTargetMode; private int mAccountNumber; private boolean mSaveAllHeaders; + private boolean mAutoUploadOnMove; private boolean mPushPollOnConnect; private boolean mNotifySync; private ScrollButtons mScrollMessageViewButtons; @@ -206,6 +208,7 @@ public class Account implements BaseAccount { mAutomaticCheckIntervalMinutes = -1; mIdleRefreshMinutes = 24; mSaveAllHeaders = true; + mAutoUploadOnMove = true; mPushPollOnConnect = true; mDisplayCount = K9.DEFAULT_VISIBLE_LIMIT; mAccountNumber = -1; @@ -284,6 +287,7 @@ public class Account implements BaseAccount { mAutomaticCheckIntervalMinutes = prefs.getInt(mUuid + ".automaticCheckIntervalMinutes", -1); mIdleRefreshMinutes = prefs.getInt(mUuid + ".idleRefreshMinutes", 24); mSaveAllHeaders = prefs.getBoolean(mUuid + ".saveAllHeaders", true); + mAutoUploadOnMove = prefs.getBoolean(mUuid + ".autoUploadOnMove", true); mPushPollOnConnect = prefs.getBoolean(mUuid + ".pushPollOnConnect", true); mDisplayCount = prefs.getInt(mUuid + ".displayCount", K9.DEFAULT_VISIBLE_LIMIT); if (mDisplayCount < 0) { @@ -445,6 +449,7 @@ public class Account implements BaseAccount { editor.remove(mUuid + ".automaticCheckIntervalMinutes"); editor.remove(mUuid + ".pushPollOnConnect"); editor.remove(mUuid + ".saveAllHeaders"); + editor.remove(mUuid + ".autoUploadOnMove"); editor.remove(mUuid + ".idleRefreshMinutes"); editor.remove(mUuid + ".lastAutomaticCheckTime"); editor.remove(mUuid + ".latestOldMessageSeenTime"); @@ -601,6 +606,7 @@ public class Account implements BaseAccount { editor.putInt(mUuid + ".automaticCheckIntervalMinutes", mAutomaticCheckIntervalMinutes); editor.putInt(mUuid + ".idleRefreshMinutes", mIdleRefreshMinutes); editor.putBoolean(mUuid + ".saveAllHeaders", mSaveAllHeaders); + editor.putBoolean(mUuid + ".autoUploadOnMove", mAutoUploadOnMove); editor.putBoolean(mUuid + ".pushPollOnConnect", mPushPollOnConnect); editor.putInt(mUuid + ".displayCount", mDisplayCount); editor.putLong(mUuid + ".lastAutomaticCheckTime", mLastAutomaticCheckTime); @@ -1305,6 +1311,14 @@ public class Account implements BaseAccount { mSaveAllHeaders = saveAllHeaders; } + public synchronized boolean isAutoUploadOnMove() { + return mAutoUploadOnMove; + } + + public synchronized void setAutoUploadOnMove(boolean autoUploadOnMove) { + mAutoUploadOnMove = autoUploadOnMove; + } + /** * Are we storing out localStore on the SD-card instead of the local device * memory?
diff --git a/src/com/fsck/k9/K9.java b/src/com/fsck/k9/K9.java index d48c79334..0ba938233 100644 --- a/src/com/fsck/k9/K9.java +++ b/src/com/fsck/k9/K9.java @@ -179,7 +179,7 @@ public class K9 extends Application { private static String mQuietTimeEnds = null; private static boolean compactLayouts = false; private static String mAttachmentDefaultPath = ""; - + private static boolean mShowAdvancedOptions; private static boolean useGalleryBugWorkaround = false; private static boolean galleryBuggy; @@ -445,6 +445,7 @@ public class K9 extends Application { editor.putBoolean("compactLayouts", compactLayouts); editor.putString("attachmentdefaultpath", mAttachmentDefaultPath); + editor.putBoolean("showAdvancedOptions", mShowAdvancedOptions); fontSizes.save(editor); } @@ -575,6 +576,7 @@ public class K9 extends Application { compactLayouts = sprefs.getBoolean("compactLayouts", false); mAttachmentDefaultPath = sprefs.getString("attachmentdefaultpath", Environment.getExternalStorageDirectory().toString()); + mShowAdvancedOptions = sprefs.getBoolean("showAdvancedOptions", false); fontSizes.load(sprefs); try { @@ -1016,4 +1018,14 @@ public class K9 extends Application { public static void setAttachmentDefaultPath(String attachmentDefaultPath) { K9.mAttachmentDefaultPath = attachmentDefaultPath; } + + public static boolean isShowAdvancedOptions() { + return mShowAdvancedOptions; + } + + public static void setShowAdvancedOptions(boolean showAdvancedOptions) { + mShowAdvancedOptions = showAdvancedOptions; + } + + } diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index 80777aedb..57d42f2c0 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -15,6 +15,7 @@ import android.view.View; import android.view.Window; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import android.widget.CheckBox; import android.widget.EditText; import android.widget.Filter; import android.widget.ListView; @@ -33,6 +34,8 @@ import com.fsck.k9.mail.store.WebDavStore; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.HashMap; +import java.util.Map; public class ChooseFolder extends K9ListActivity { String mFolder; @@ -183,6 +186,9 @@ public class ChooseFolder extends K9ListActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); getMenuInflater().inflate(R.menu.folder_select_option, menu); + if (mAccount.getStoreUri().startsWith("webdav")) { + menu.findItem(R.id.create_folder).setVisible(false); + } return true; } @@ -283,21 +289,37 @@ public class ChooseFolder extends K9ListActivity { } /* - Show a dialog to create a new folder on the remote Store. - Currently only IMAP is supported. - Exactly the same as activity.FolderList.onCreateFolder(). + Show a dialog to create a new folder. + Currently only IMAP and Pop3 supported. + IMAP folders are both remote and local. Pop3 folders are only local. + Exactly the same as activity.FolderList.onCreateFolder(), plus one line, plus s/mInflater/getLayoutInflater()/; */ private void onCreateFolder() { - final EditText input = new EditText(this); AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.create_folder_action); - dialog.setView(input); + View view = getLayoutInflater().inflate(R.layout.create_folder, null); + final EditText input = (EditText) view.findViewById(R.id.create_folder_text); + final CheckBox checkBox = (CheckBox) view.findViewById(R.id.create_folder_local); + if (mAccount.getStoreUri().startsWith("pop3") || !K9.isShowAdvancedOptions()) { + checkBox.setVisibility(View.GONE); + } + dialog.setView(view); dialog.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String folderName = input.getText().toString().trim(); + if (folderName.matches("")) { + Toast.makeText(getApplication(), "Folder name not given!", Toast.LENGTH_LONG).show(); + return; + } try { Store store = mAccount.getRemoteStore(); - if (store instanceof ImapStore) { + if (store instanceof Pop3Store || checkBox.isChecked()) { + boolean result = mAccount.getLocalStore().createFolder(folderName, true); + String toastText = "Creation of folder \"" + folderName + + ((result) ? "\" succeeded." : "\" failed."); + Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); + onRefresh(false); + } else if (store instanceof ImapStore) { boolean result = ((ImapStore)store).createFolder(folderName); String toastText = "Creation of folder \"" + folderName + ((result) ? "\" succeeded." : "\" failed."); @@ -307,12 +329,6 @@ public class ChooseFolder extends K9ListActivity { } else if (store instanceof WebDavStore) { String toastText = "Creating WebDav Folders not currently implemented."; Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); - } else if (store instanceof Pop3Store) { - boolean result = mAccount.getLocalStore().createFolder(folderName, true); - String toastText = "Creation of folder \"" + folderName + - ((result) ? "\" succeeded." : "\" failed."); - Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); - onRefresh(false); } else { Log.d(K9.LOG_TAG, "Unhandled store type " + store.getClass()); } @@ -323,9 +339,7 @@ public class ChooseFolder extends K9ListActivity { } }); dialog.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked cancel so do some stuff */ - } + public void onClick(DialogInterface dialog, int whichButton) {} }); dialog.show(); } diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java index 61be653c4..1786f7dd4 100644 --- a/src/com/fsck/k9/activity/FolderList.java +++ b/src/com/fsck/k9/activity/FolderList.java @@ -45,8 +45,10 @@ import com.fsck.k9.service.MailService; import java.util.ArrayList; import java.util.Collections; import java.util.Date; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; /** * FolderList is the primary user interface for the program. This @@ -477,16 +479,31 @@ public class FolderList extends K9ListActivity { Exactly the same as activity.ChooseFolder.onCreateFolder(). */ private void onCreateFolder() { - final EditText input = new EditText(this); AlertDialog.Builder dialog = new AlertDialog.Builder(this); dialog.setTitle(R.string.create_folder_action); - dialog.setView(input); + View view = mInflater.inflate(R.layout.create_folder, null); + final EditText input = (EditText) view.findViewById(R.id.create_folder_text); + final CheckBox checkBox = (CheckBox) view.findViewById(R.id.create_folder_local); + if (mAccount.getStoreUri().startsWith("pop3") || !K9.isShowAdvancedOptions()) { + checkBox.setVisibility(View.GONE); + } + dialog.setView(view); dialog.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { String folderName = input.getText().toString().trim(); + if (folderName.matches("")) { + Toast.makeText(getApplication(), "Folder name not given!", Toast.LENGTH_LONG).show(); + return; + } try { Store store = mAccount.getRemoteStore(); - if (store instanceof ImapStore) { + if (store instanceof Pop3Store || checkBox.isChecked()) { + boolean result = mAccount.getLocalStore().createFolder(folderName, true); + String toastText = "Creation of folder \"" + folderName + + ((result) ? "\" succeeded." : "\" failed."); + Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); + onRefresh(false); + } else if (store instanceof ImapStore) { boolean result = ((ImapStore)store).createFolder(folderName); String toastText = "Creation of folder \"" + folderName + ((result) ? "\" succeeded." : "\" failed."); @@ -495,12 +512,6 @@ public class FolderList extends K9ListActivity { } else if (store instanceof WebDavStore) { String toastText = "Creating WebDav Folders not currently implemented."; Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); - } else if (store instanceof Pop3Store) { - boolean result = mAccount.getLocalStore().createFolder(folderName, true); - String toastText = "Creation of folder \"" + folderName + - ((result) ? "\" succeeded." : "\" failed."); - Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); - onRefresh(false); } else { Log.d(K9.LOG_TAG, "Unhandled store type " + store.getClass()); } @@ -511,9 +522,7 @@ public class FolderList extends K9ListActivity { } }); dialog.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked cancel so do some stuff */ - } + public void onClick(DialogInterface dialog, int whichButton) {} }); dialog.show(); } @@ -544,33 +553,65 @@ public class FolderList extends K9ListActivity { MessagingController.getInstance(getApplication()).expunge(account, folderName, null); } - private void onClearFolder(Account account, String folderName) { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setCancelable(false); + dialog.setTitle(R.string.clear_local_folder_action); + View view = mInflater.inflate(R.layout.clear_local_folder, null); + final TextView text = (TextView) view.findViewById(R.id.clear_local_folder_text); + final TextView textLocal = (TextView) view.findViewById(R.id.clear_local_only_folder_text); + final CheckBox checkBox = (CheckBox) view.findViewById(R.id.clear_local_folder_all); + // There has to be a cheaper way to get at the localFolder object than this LocalFolder localFolder = null; try { if (account == null || folderName == null || !account.isAvailable(FolderList.this)) { - Log.i(K9.LOG_TAG, "not clear folder of unavailable account"); + Log.i(K9.LOG_TAG, "Not clearing folder of unavailable account"); return; } localFolder = account.getLocalStore().getFolder(folderName); localFolder.open(Folder.OpenMode.READ_WRITE); - localFolder.clearAllMessages(); + if (localFolder.isLocalOnly()) { + checkBox.setChecked(true); + checkBox.setVisibility(View.GONE); + text.setVisibility(View.GONE); + } else { + textLocal.setVisibility(View.GONE); + } } catch (Exception e) { Log.e(K9.LOG_TAG, "Exception while clearing folder", e); - } finally { if (localFolder != null) { localFolder.close(); } + return; } + final LocalFolder localFolderFinal = localFolder; - onRefresh(!REFRESH_REMOTE); + dialog.setView(view); + dialog.setPositiveButton(R.string.okay_action, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + try { + localFolderFinal.clearAllMessages(checkBox.isChecked()); + } catch (MessagingException e) { + Log.e(K9.LOG_TAG, "Exception while clearing folder", e); + } finally { + if (localFolderFinal != null) { + localFolderFinal.close(); + } + onRefresh(!REFRESH_REMOTE); + } + } + }); + dialog.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + if (localFolderFinal != null) { + localFolderFinal.close(); + } + } + }); + dialog.show(); } - - - - private void sendMail(Account account) { MessagingController.getInstance(getApplication()).sendPendingMessages(account, mAdapter.mListener); } @@ -759,7 +800,17 @@ public class FolderList extends K9ListActivity { String folderName = input.getText().toString().trim(); try { Store store = mAccount.getRemoteStore(); - if (store instanceof ImapStore) { + boolean isLocalOnly = ((LocalFolder)folder.folder).isLocalOnly(); + if (store instanceof Pop3Store || isLocalOnly) { + boolean result = mAccount.getLocalStore().renameFolder(folder.name, folderName); + if (result && mAccount.isSpecialFolder(folder.name)) { + resetSpecialFolders(folder.name, folderName); + } + String toastText = "Renaming folder \"" + folder.name + "\" to \"" + folderName + + ((result) ? "\" succeeded." : "\" failed."); + Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); + onRefresh(false); + } else if (store instanceof ImapStore) { boolean result = false; if (((ImapStore)store).renameFolder(folder.name, folderName)) { result = mAccount.getLocalStore().renameFolder(folder.name, folderName); @@ -779,28 +830,17 @@ public class FolderList extends K9ListActivity { } else if (store instanceof WebDavStore) { String toastText = "Deleting WebDav Folders not currently implemented."; Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); - } else if (store instanceof Pop3Store) { - boolean result = mAccount.getLocalStore().renameFolder(folder.name, folderName); - if (result && mAccount.isSpecialFolder(folder.name)) { - resetSpecialFolders(folder.name, folderName); - } - String toastText = "Renaming folder \"" + folder.name + "\" to \"" + folderName + - ((result) ? "\" succeeded." : "\" failed."); - Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); - onRefresh(false); } else { Log.d(K9.LOG_TAG, "Unhandled store type " + store.getClass()); } } catch (com.fsck.k9.mail.MessagingException me) { - Log.e(K9.LOG_TAG, "MessagingException trying to deletefolder \"" + + Log.e(K9.LOG_TAG, "MessagingException trying to rename folder \"" + folder.name + "\": " + me); } } }); dialog.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked cancel so do some stuff */ - } + public void onClick(DialogInterface dialog, int whichButton) {} }); dialog.show(); @@ -819,7 +859,17 @@ public class FolderList extends K9ListActivity { public void onClick(DialogInterface dialog, int whichButton) { try { Store store = mAccount.getRemoteStore(); - if (store instanceof ImapStore) { + boolean isLocalOnly = ((LocalFolder)folder.folder).isLocalOnly(); + if (store instanceof Pop3Store || isLocalOnly) { + boolean result = mAccount.getLocalStore().delete(folder.name); + if (result && mAccount.isSpecialFolder(folder.name)) { + resetSpecialFolders(folder.name, K9.FOLDER_NONE); + } + String toastText = "Deletion of folder \"" + folder.name + + ((result) ? "\" succeeded." : "\" failed."); + Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); + onRefresh(false); + } else if (store instanceof ImapStore) { boolean result = ((ImapStore)store).delete(folder.name); if (result) { mAccount.getLocalStore().delete(folder.name); @@ -834,28 +884,17 @@ public class FolderList extends K9ListActivity { } else if (store instanceof WebDavStore) { String toastText = "Deleting WebDav Folders not currently implemented."; Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); - } else if (store instanceof Pop3Store) { - boolean result = mAccount.getLocalStore().delete(folder.name); - if (result && mAccount.isSpecialFolder(folder.name)) { - resetSpecialFolders(folder.name, K9.FOLDER_NONE); - } - String toastText = "Deletion of folder \"" + folder.name + - ((result) ? "\" succeeded." : "\" failed."); - Toast.makeText(getApplication(), toastText, Toast.LENGTH_LONG).show(); - onRefresh(false); } else { Log.d(K9.LOG_TAG, "Unhandled store type " + store.getClass()); } } catch (com.fsck.k9.mail.MessagingException me) { - Log.e(K9.LOG_TAG, "MessagingException trying to deletefolder \"" + + Log.e(K9.LOG_TAG, "MessagingException trying to delete folder \"" + folder.name + "\": " + me); } } }); dialog.setNegativeButton(R.string.cancel_action, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked cancel so do some stuff */ - } + public void onClick(DialogInterface dialog, int whichButton) {} }); dialog.show(); } @@ -921,18 +960,28 @@ public class FolderList extends K9ListActivity { getMenuInflater().inflate(R.menu.folder_context, menu); FolderInfoHolder folder = (FolderInfoHolder) mAdapter.getItem(info.position); + boolean isLocalOnly = ((LocalFolder)folder.folder).isLocalOnly(); + boolean isExpungeCapable = false; + try { + isExpungeCapable = mAccount.getRemoteStore().isExpungeCapable(); + } catch (com.fsck.k9.mail.MessagingException e) { + Log.e(K9.LOG_TAG, "MessagingException trying to get remote store " + e); + } menu.setHeaderTitle(folder.displayName); - if (!folder.name.equals(mAccount.getTrashFolderName())) + if (!folder.name.equals(mAccount.getTrashFolderName())) { menu.findItem(R.id.empty_trash).setVisible(false); - + } if (folder.name.equals(mAccount.getOutboxFolderName())) { menu.findItem(R.id.check_mail).setVisible(false); } else { menu.findItem(R.id.send_messages).setVisible(false); } - if (K9.ERROR_FOLDER_NAME.equals(folder.name)) { + if (isLocalOnly) { + menu.findItem(R.id.check_mail).setVisible(false); + } + if (K9.ERROR_FOLDER_NAME.equals(folder.name) || isLocalOnly || !isExpungeCapable) { menu.findItem(R.id.expunge).setVisible(false); } @@ -946,6 +995,11 @@ public class FolderList extends K9ListActivity { menu.findItem(R.id.rename_folder).setVisible(false); menu.findItem(R.id.delete_folder).setVisible(false); } + if (mAccount.getStoreUri().startsWith("webdav")) { + menu.findItem(R.id.create_folder).setVisible(false); + menu.findItem(R.id.rename_folder).setVisible(false); + menu.findItem(R.id.delete_folder).setVisible(false); + } menu.setHeaderTitle(folder.displayName); } diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java index ffdaafcc5..c54966acc 100644 --- a/src/com/fsck/k9/activity/MessageList.java +++ b/src/com/fsck/k9/activity/MessageList.java @@ -245,11 +245,11 @@ public class MessageList private boolean mTouchView = true; private int mPreviewLines = 0; - private MessageListAdapter mAdapter; private View mFooterView; private FolderInfoHolder mCurrentFolder; + private boolean mLocalOnly = false; private LayoutInflater mInflater; @@ -729,8 +729,26 @@ public class MessageList mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount); } - // Hide "Load up to x more" footer for search views - mFooterView.setVisibility((mQueryString != null) ? View.GONE : View.VISIBLE); + // ASH this seems wrong, but it works for now. + if (mCurrentFolder != null) { + LocalFolder folder = (LocalFolder)mCurrentFolder.folder; + if (folder != null) { + try { + folder.open(Folder.OpenMode.READ_ONLY); + mLocalOnly = folder.isLocalOnly(); + } catch(com.fsck.k9.mail.MessagingException e) { + Log.e("ASH", "ack! " + e); + } + } + Log.d("ASH", "mLocalOnly = " + mLocalOnly + " for " + mCurrentFolder.name); + } else { + // should mLocalOnly be true or false or ??? + // if true, it hides "Load up to x more", but this should be hidden anyway. + // it also hides R.id.check_mail and R.id.expunge + } + + // Hide "Load up to x more" footer for search views and local-only folders + mFooterView.setVisibility((mQueryString != null || mLocalOnly) ? View.GONE : View.VISIBLE); mController = MessagingController.getInstance(getApplication()); mListView.setAdapter(mAdapter); @@ -1527,6 +1545,10 @@ public class MessageList onMove(selection); return true; } + case R.id.batch_upload_op: { + onUpload(selection); + return true; + } case R.id.expunge: { if (mCurrentFolder != null) { onExpunge(mAccount, mCurrentFolder.name); @@ -1569,18 +1591,28 @@ public class MessageList menu.findItem(R.id.batch_spam_op).setVisible(false); menu.findItem(R.id.batch_move_op).setVisible(false); menu.findItem(R.id.batch_copy_op).setVisible(false); + menu.findItem(R.id.batch_upload_op).setVisible(false); menu.findItem(R.id.check_mail).setVisible(false); menu.findItem(R.id.send_messages).setVisible(false); menu.findItem(R.id.folder_settings).setVisible(false); menu.findItem(R.id.account_settings).setVisible(false); } else { + boolean isExpungeCapable = false; + try { + isExpungeCapable = mAccount.getRemoteStore().isExpungeCapable(); + } catch (com.fsck.k9.mail.MessagingException e) { + Log.e(K9.LOG_TAG, "MessagingException trying to get remote store " + e); + } if (mCurrentFolder != null && mCurrentFolder.name.equals(mAccount.getOutboxFolderName())) { menu.findItem(R.id.check_mail).setVisible(false); } else { menu.findItem(R.id.send_messages).setVisible(false); } - - if (mCurrentFolder != null && K9.ERROR_FOLDER_NAME.equals(mCurrentFolder.name)) { + if (mLocalOnly) { + menu.findItem(R.id.check_mail).setEnabled(false); + } + if ((mCurrentFolder != null && K9.ERROR_FOLDER_NAME.equals(mCurrentFolder.name)) || + mLocalOnly || !isExpungeCapable) { menu.findItem(R.id.expunge).setVisible(false); } if (K9.FOLDER_NONE.equalsIgnoreCase(mAccount.getArchiveFolderName())) { @@ -1589,6 +1621,14 @@ public class MessageList if (K9.FOLDER_NONE.equalsIgnoreCase(mAccount.getSpamFolderName())) { menu.findItem(R.id.batch_spam_op).setVisible(false); } + try { + if (((com.fsck.k9.mail.store.LocalStore.LocalFolder)mCurrentFolder.folder).isLocalOnly() || + !mAccount.getRemoteStore().isAppendCapable()) { + menu.findItem(R.id.batch_upload_op).setVisible(false); + } + } catch (com.fsck.k9.mail.MessagingException e) { + Log.e(K9.LOG_TAG, "Error trying to get remote store: " + e); + } } boolean newFlagState = computeBatchDirection(true); @@ -1677,6 +1717,10 @@ public class MessageList onCopy(selection); break; } + case R.id.upload: { + onUpload(selection); + break; + } case R.id.send_alternate: { onSendAlternate(mAccount, holder); break; @@ -1776,6 +1820,16 @@ public class MessageList menu.findItem(R.id.spam).setVisible(false); } + try { + if (!message.uid.startsWith(K9.LOCAL_UID_PREFIX) || mQueryString != null || + ((com.fsck.k9.mail.store.LocalStore.LocalFolder)message.message.getFolder()).isLocalOnly() || + !account.getRemoteStore().isAppendCapable()) { + menu.findItem(R.id.upload).setVisible(false); + } + } catch (com.fsck.k9.mail.MessagingException e) { + Log.e(K9.LOG_TAG, "Error trying to get remote store: " + e); + } + if (message.selected) { menu.findItem(R.id.select).setVisible(false); menu.findItem(R.id.deselect).setVisible(true); @@ -2262,6 +2316,10 @@ public class MessageList holder.chip.setBackgroundDrawable(message.message.getFolder().getAccount().generateColorChip().drawable()); holder.chip.getBackground().setAlpha(message.read ? 127 : 255); view.getBackground().setAlpha(message.downloaded ? 0 : 127); + if (message.uid.startsWith(K9.LOCAL_UID_PREFIX)) { + view.setBackgroundColor(message.message.getFolder().getAccount().getChipColor()); + view.getBackground().setAlpha(31); + } if ((message.message.getSubject() == null) || message.message.getSubject().equals("")) { holder.subject.setText(getText(R.string.general_no_subject)); @@ -2662,6 +2720,31 @@ public class MessageList displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder, holders); } + /** + * Append messages to server. + * + * @param holders + * Never {@code null}. + */ + private void onUpload(final List holders) { + if (holders.isEmpty()) { + return; + } + boolean isAppendCapable = false; + try { + isAppendCapable = mAccount.getRemoteStore().isAppendCapable(); + } catch (com.fsck.k9.mail.MessagingException e) { + Log.e(K9.LOG_TAG, "Error trying to get remote store: " + e); + } + for (MessageInfoHolder holder : holders) { + if (holder.uid.startsWith(K9.LOCAL_UID_PREFIX) && !((LocalFolder)holder.folder.folder).isLocalOnly() && isAppendCapable) { + mController.saveMessage(mAccount, holder.message, holder.folder.name); + } else { + Log.d("ASH", "cannot sync " + holder.folder.name + " " + holder.uid + " " + holder.message.getSubject()); + } + } + } + /** * Helper method to manage the invocation of * {@link #startActivityForResult(Intent, int)} for a folder operation diff --git a/src/com/fsck/k9/activity/MessageView.java b/src/com/fsck/k9/activity/MessageView.java index 6642561e3..86b9ee6ec 100644 --- a/src/com/fsck/k9/activity/MessageView.java +++ b/src/com/fsck/k9/activity/MessageView.java @@ -752,6 +752,19 @@ public class MessageView extends K9Activity implements OnClickListener { startActivityForResult(intent, activity); } + private void onUpload() { + boolean isAppendCapable = false; + try { + isAppendCapable = mAccount.getRemoteStore().isAppendCapable(); + } catch (com.fsck.k9.mail.MessagingException e) { + Log.e(K9.LOG_TAG, "Error trying to get remote store: " + e); + } + if (mMessageReference.uid.startsWith(K9.LOCAL_UID_PREFIX) && !((com.fsck.k9.mail.store.LocalStore.LocalFolder)mMessage.getFolder()).isLocalOnly() && isAppendCapable) { + mController.saveMessage(mAccount, mMessage, mMessageReference.folderName); + } else { + Log.d("ASH", "cannot sync " + mMessageReference.folderName + " " + mMessageReference.uid + " " + mMessage.getSubject()); + } + } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { @@ -965,6 +978,9 @@ public class MessageView extends K9Activity implements OnClickListener { case R.id.copy: onCopy(); break; + case R.id.upload: + onUpload(); + break; case R.id.show_full_header: runOnUiThread(new Runnable() { @Override @@ -993,6 +1009,15 @@ public class MessageView extends K9Activity implements OnClickListener { if (K9.FOLDER_NONE.equalsIgnoreCase(mAccount.getSpamFolderName())) { menu.findItem(R.id.spam).setVisible(false); } + try { + if (!mMessage.getUid().startsWith(K9.LOCAL_UID_PREFIX) || + ((com.fsck.k9.mail.store.LocalStore.LocalFolder)mMessage.getFolder()).isLocalOnly() || + !mAccount.getRemoteStore().isAppendCapable()) { + menu.findItem(R.id.upload).setVisible(false); + } + } catch (com.fsck.k9.mail.MessagingException e) { + Log.e(K9.LOG_TAG, "Error trying to get remote store: " + e); + } return true; } @@ -1057,6 +1082,8 @@ public class MessageView extends K9Activity implements OnClickListener { } public void displayMessageBody(final Account account, final String folder, final String uid, final Message message) { +Log.d("ASH", MessageView.this.mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL) + " " + MessageView.this.mMessage.isSet(Flag.X_DOWNLOADED_FULL)); +Log.d("ASH", message.isSet(Flag.X_DOWNLOADED_PARTIAL) + " " + message.isSet(Flag.X_DOWNLOADED_FULL)); runOnUiThread(new Runnable() { public void run() { mTopView.scrollTo(0, 0); diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java index d8c47e12c..4dbce46dc 100644 --- a/src/com/fsck/k9/activity/setup/AccountSettings.java +++ b/src/com/fsck/k9/activity/setup/AccountSettings.java @@ -86,6 +86,7 @@ public class AccountSettings extends K9PreferenceActivity { private static final String PREFERENCE_MESSAGE_AGE = "account_message_age"; private static final String PREFERENCE_MESSAGE_SIZE = "account_autodownload_size"; private static final String PREFERENCE_SAVE_ALL_HEADERS = "account_save_all_headers"; + private static final String PREFERENCE_AUTO_UPLOAD_ON_MOVE = "account_auto_upload_on_move"; private static final String PREFERENCE_MESSAGE_FORMAT = "message_format"; private static final String PREFERENCE_MESSAGE_READ_RECEIPT = "message_read_receipt"; private static final String PREFERENCE_QUOTE_PREFIX = "account_quote_prefix"; @@ -112,6 +113,7 @@ public class AccountSettings extends K9PreferenceActivity { private Account mAccount; private boolean mIsPushCapable = false; private boolean mIsExpungeCapable = false; + private boolean mIsAppendCapable = false; private PreferenceScreen mComposingScreen; @@ -155,6 +157,7 @@ public class AccountSettings extends K9PreferenceActivity { private CheckBoxPreference mStripSignature; private CheckBoxPreference mSyncRemoteDeletions; private CheckBoxPreference mSaveAllHeaders; + private CheckBoxPreference mAutoUploadOnMove; private CheckBoxPreference mPushPollOnConnect; private ListPreference mIdleRefreshPeriod; private ListPreference mMaxPushFolders; @@ -189,6 +192,7 @@ public class AccountSettings extends K9PreferenceActivity { final Store store = mAccount.getRemoteStore(); mIsPushCapable = store.isPushCapable(); mIsExpungeCapable = store.isExpungeCapable(); + mIsAppendCapable = store.isAppendCapable(); } catch (Exception e) { Log.e(K9.LOG_TAG, "Could not get remote store", e); } @@ -362,6 +366,13 @@ public class AccountSettings extends K9PreferenceActivity { mSaveAllHeaders = (CheckBoxPreference) findPreference(PREFERENCE_SAVE_ALL_HEADERS); mSaveAllHeaders.setChecked(mAccount.saveAllHeaders()); + mAutoUploadOnMove = (CheckBoxPreference) findPreference(PREFERENCE_AUTO_UPLOAD_ON_MOVE); + mAutoUploadOnMove.setChecked(mAccount.isAutoUploadOnMove() && mIsAppendCapable); + if (!mIsAppendCapable || !K9.isShowAdvancedOptions()) { + ((PreferenceScreen) findPreference(PREFERENCE_SCREEN_INCOMING)). + removePreference(mAutoUploadOnMove); + } + mSearchableFolders = (ListPreference) findPreference(PREFERENCE_SEARCHABLE_FOLDERS); mSearchableFolders.setValue(mAccount.getSearchableFolders().name()); mSearchableFolders.setSummary(mSearchableFolders.getEntry()); @@ -721,6 +732,7 @@ public class AccountSettings extends K9PreferenceActivity { } mAccount.setSyncRemoteDeletions(mSyncRemoteDeletions.isChecked()); mAccount.setSaveAllHeaders(mSaveAllHeaders.isChecked()); + mAccount.setAutoUploadOnMove(mAutoUploadOnMove.isChecked()); mAccount.setSearchableFolders(Account.Searchable.valueOf(mSearchableFolders.getValue())); mAccount.setMessageFormat(Account.MessageFormat.valueOf(mMessageFormat.getValue())); mAccount.setMessageReadReceipt(mMessageReadReceipt.isChecked()); diff --git a/src/com/fsck/k9/activity/setup/FolderSettings.java b/src/com/fsck/k9/activity/setup/FolderSettings.java index 2d7c67234..4f61c6034 100644 --- a/src/com/fsck/k9/activity/setup/FolderSettings.java +++ b/src/com/fsck/k9/activity/setup/FolderSettings.java @@ -7,15 +7,19 @@ import android.os.Bundle; import android.preference.CheckBoxPreference; import android.preference.ListPreference; import android.preference.Preference; +import android.preference.PreferenceCategory; import android.util.Log; import com.fsck.k9.*; import com.fsck.k9.activity.K9PreferenceActivity; +import com.fsck.k9.controller.MessagingController; +import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Folder.FolderClass; import com.fsck.k9.mail.Folder.OpenMode; import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.Store; import com.fsck.k9.mail.store.LocalStore; import com.fsck.k9.mail.store.LocalStore.LocalFolder; +import com.fsck.k9.mail.store.Pop3Store; import com.fsck.k9.service.MailService; public class FolderSettings extends K9PreferenceActivity { @@ -29,11 +33,14 @@ public class FolderSettings extends K9PreferenceActivity { private static final String PREFERENCE_PUSH_CLASS = "folder_settings_folder_push_mode"; private static final String PREFERENCE_IN_TOP_GROUP = "folder_settings_in_top_group"; private static final String PREFERENCE_INTEGRATE = "folder_settings_include_in_integrated_inbox"; + private static final String PREFERENCE_LOCAL_ONLY = "folder_settings_local_only"; + private Account mAccount; private LocalFolder mFolder; private CheckBoxPreference mInTopGroup; private CheckBoxPreference mIntegrate; + private CheckBoxPreference mLocalOnly; private ListPreference mDisplayClass; private ListPreference mSyncClass; private ListPreference mPushClass; @@ -51,7 +58,7 @@ public class FolderSettings extends K9PreferenceActivity { String folderName = (String)getIntent().getSerializableExtra(EXTRA_FOLDER_NAME); String accountUuid = getIntent().getStringExtra(EXTRA_ACCOUNT); - Account mAccount = Preferences.getPreferences(this).getAccount(accountUuid); + mAccount = Preferences.getPreferences(this).getAccount(accountUuid); try { LocalStore localStore = mAccount.getLocalStore(); @@ -73,7 +80,7 @@ public class FolderSettings extends K9PreferenceActivity { addPreferencesFromResource(R.xml.folder_settings_preferences); - Preference category = findPreference(PREFERENCE_TOP_CATERGORY); + PreferenceCategory category = (PreferenceCategory)findPreference(PREFERENCE_TOP_CATERGORY); category.setTitle(folderName); @@ -81,6 +88,8 @@ public class FolderSettings extends K9PreferenceActivity { mInTopGroup.setChecked(mFolder.isInTopGroup()); mIntegrate = (CheckBoxPreference)findPreference(PREFERENCE_INTEGRATE); mIntegrate.setChecked(mFolder.isIntegrate()); + mLocalOnly = (CheckBoxPreference)findPreference(PREFERENCE_LOCAL_ONLY); + mLocalOnly.setChecked(mFolder.isLocalOnly()); mDisplayClass = (ListPreference) findPreference(PREFERENCE_DISPLAY_CLASS); mDisplayClass.setValue(mFolder.getDisplayClass().name()); @@ -96,6 +105,7 @@ public class FolderSettings extends K9PreferenceActivity { }); mSyncClass = (ListPreference) findPreference(PREFERENCE_SYNC_CLASS); + mSyncClass.setEnabled(!mLocalOnly.isChecked()); mSyncClass.setValue(mFolder.getRawSyncClass().name()); mSyncClass.setSummary(mSyncClass.getEntry()); mSyncClass.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @@ -107,9 +117,12 @@ public class FolderSettings extends K9PreferenceActivity { return false; } }); + if (store instanceof Pop3Store) { + category.removePreference(mSyncClass); + } mPushClass = (ListPreference) findPreference(PREFERENCE_PUSH_CLASS); - mPushClass.setEnabled(isPushCapable); + mPushClass.setEnabled(isPushCapable && !mLocalOnly.isChecked()); mPushClass.setValue(mFolder.getRawPushClass().name()); mPushClass.setSummary(mPushClass.getEntry()); mPushClass.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { @@ -121,20 +134,55 @@ public class FolderSettings extends K9PreferenceActivity { return false; } }); + if (store instanceof Pop3Store) { + category.removePreference(mPushClass); + } + + mLocalOnly = (CheckBoxPreference)findPreference(PREFERENCE_LOCAL_ONLY); + mLocalOnly.setChecked(mFolder.isLocalOnly()); + if (store instanceof Pop3Store || mAccount.getInboxFolderName().equals(folderName) || + mAccount.getOutboxFolderName().equals(folderName)) { + mLocalOnly.setEnabled(false); + } + if (!K9.isShowAdvancedOptions()) {// ASH disabled for testing: || store instanceof Pop3Store) { + category.removePreference(mLocalOnly); + } + } private void saveSettings() throws MessagingException { mFolder.setInTopGroup(mInTopGroup.isChecked()); mFolder.setIntegrate(mIntegrate.isChecked()); + boolean oldIsLocalOnly = mFolder.isLocalOnly(); + mFolder.setLocalOnly(mLocalOnly.isChecked()); // We call getPushClass() because display class changes can affect push class when push class is set to inherit FolderClass oldPushClass = mFolder.getPushClass(); FolderClass oldDisplayClass = mFolder.getDisplayClass(); mFolder.setDisplayClass(FolderClass.valueOf(mDisplayClass.getValue())); - mFolder.setSyncClass(FolderClass.valueOf(mSyncClass.getValue())); - mFolder.setPushClass(FolderClass.valueOf(mPushClass.getValue())); + if (mLocalOnly.isChecked()) { + mFolder.setSyncClass(FolderClass.NO_CLASS); + mFolder.setPushClass(FolderClass.NO_CLASS); + } else { + mFolder.setSyncClass(FolderClass.valueOf(mSyncClass.getValue())); + mFolder.setPushClass(FolderClass.valueOf(mPushClass.getValue())); + } mFolder.save(); + if (!oldIsLocalOnly && mFolder.isLocalOnly()) { + Log.w(K9.LOG_TAG, "Changing UIDs of messages in folder " + mFolder.getName() + + " to local UIDs."); + MessagingController.getInstance(getApplication()).localizeUids(mFolder); + } else if (oldIsLocalOnly && !mFolder.isLocalOnly()) { + // create folder if it does not exist. + Folder folder = mAccount.getRemoteStore().getFolder(mFolder.getName()); + folder.close(); + if (!folder.exists()) { + Log.w(K9.LOG_TAG, "creating remote folder " + mFolder.getName()); + folder.create(); + } + } + FolderClass newPushClass = mFolder.getPushClass(); FolderClass newDisplayClass = mFolder.getDisplayClass(); diff --git a/src/com/fsck/k9/activity/setup/Prefs.java b/src/com/fsck/k9/activity/setup/Prefs.java index 1b359b808..4aea80cfb 100644 --- a/src/com/fsck/k9/activity/setup/Prefs.java +++ b/src/com/fsck/k9/activity/setup/Prefs.java @@ -67,6 +67,7 @@ public class Prefs extends K9PreferenceActivity { private static final String PREFERENCE_MESSAGELIST_CONTACT_NAME_COLOR = "messagelist_contact_name_color"; private static final String PREFERENCE_MESSAGEVIEW_FIXEDWIDTH = "messageview_fixedwidth_font"; private static final String PREFERENCE_COMPACT_LAYOUTS = "compact_layouts"; + private static final String PREFERENCE_SHOW_ADVANCED_OPTIONS = "show_advanced_options"; private static final String PREFERENCE_MESSAGEVIEW_RETURN_TO_LIST = "messageview_return_to_list"; private static final String PREFERENCE_MESSAGEVIEW_SHOW_NEXT = "messageview_show_next"; @@ -115,6 +116,7 @@ public class Prefs extends K9PreferenceActivity { private CheckBoxPreference mDebugLogging; private CheckBoxPreference mSensitiveLogging; private CheckBoxPreference compactLayouts; + private CheckBoxPreference showAdvancedOptions; private CheckBoxPreference mQuietTimeEnabled; private com.fsck.k9.preferences.TimePickerPreference mQuietTimeStarts; @@ -179,6 +181,9 @@ public class Prefs extends K9PreferenceActivity { compactLayouts = (CheckBoxPreference)findPreference(PREFERENCE_COMPACT_LAYOUTS); compactLayouts.setChecked(K9.useCompactLayouts()); + showAdvancedOptions = (CheckBoxPreference)findPreference(PREFERENCE_SHOW_ADVANCED_OPTIONS); + showAdvancedOptions.setChecked(K9.isShowAdvancedOptions()); + mVolumeNavigation = (CheckBoxListPreference)findPreference(PREFERENCE_VOLUME_NAVIGATION); mVolumeNavigation.setItems(new CharSequence[] {getString(R.string.volume_navigation_message), getString(R.string.volume_navigation_list)}); mVolumeNavigation.setCheckedItems(new boolean[] {K9.useVolumeKeysForNavigationEnabled(), K9.useVolumeKeysForListNavigationEnabled()}); @@ -352,6 +357,7 @@ public class Prefs extends K9PreferenceActivity { K9.setAnimations(mAnimations.isChecked()); K9.setGesturesEnabled(mGestures.isChecked()); K9.setCompactLayouts(compactLayouts.isChecked()); + K9.setShowAdvancedOptions(showAdvancedOptions.isChecked()); K9.setUseVolumeKeysForNavigation(mVolumeNavigation.getCheckedItems()[0]); K9.setUseVolumeKeysForListNavigation(mVolumeNavigation.getCheckedItems()[1]); K9.setManageBack(mManageBack.isChecked()); diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 742020894..9a07c4850 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -27,6 +27,7 @@ import android.os.PowerManager; import android.os.Process; import android.text.TextUtils; import android.util.Log; +import android.widget.Toast; import com.fsck.k9.Account; import com.fsck.k9.AccountStats; @@ -476,20 +477,28 @@ public class MessagingController implements Runnable { localFolders = localStore.getPersonalNamespaces(false); /* - * Clear out any folders that are no longer on the remote store, - * unless they are tagged as local-only or are special folders. + * Clear out any folders that are no longer on the remote store, unless they + * are tagged as local-only or are special folders. If a folder contains + * messages with UIDs starting with K9.LOCAL_UID_PREFIX, then those messages + * are saved and the folder is converted to local-only. */ - // ASH todo: also don't clear out folders with K9.LOCAL_UID_PREFIX for (LocalFolder localFolder : localFolders) { String localFolderName = localFolder.getName(); if (!account.isSpecialFolder(localFolderName) && !remoteFolderNames.contains(localFolderName) && !localFolder.isLocalOnly()) { - localFolder.delete(false); + for (Message message : localFolder.getMessages(null)) { + if (message.getUid().startsWith(K9.LOCAL_UID_PREFIX)) { + localFolder.clearAllMessages(false); + localFolder.setLocalOnly(true); + break; + } + } + if (!localFolder.isLocalOnly()) { + localFolder.delete(false); + } } - if (remoteFolderNames.contains(localFolderName)) { - if (localFolder.isLocalOnly()) localFolder.setLocalOnly(false); - } else if (localFolder.exists()) { + if (!remoteFolderNames.contains(localFolderName) && localFolder.exists()) { if (!localFolder.isLocalOnly()) localFolder.setLocalOnly(true); } } @@ -1016,8 +1025,9 @@ public class MessagingController implements Runnable { ArrayList missingMessages = new ArrayList(); for (Message localMessage : localMessages) { String uid = localMessage.getUid(); - if (!uid.startsWith(K9.LOCAL_UID_PREFIX) && !remoteUidMap.containsKey(uid)) { - // This message used to be on the server + if ((!uid.startsWith(K9.LOCAL_UID_PREFIX) && !remoteUidMap.containsKey(uid)) || + (uid.startsWith(K9.LOCAL_UID_PREFIX) && localMessage.isSet(Flag.DELETED))) { + // This message used to be on the server, or is local and should be expunged missingMessages.add(localMessage); } } @@ -1988,6 +1998,12 @@ public class MessagingController implements Runnable { LocalStore localStore = account.getLocalStore(); localFolder = localStore.getFolder(folder); + localFolder.open(OpenMode.READ_WRITE); + + if (localFolder.isLocalOnly()) { + return; + } + LocalMessage localMessage = (LocalMessage) localFolder.getMessage(uid); if (localMessage == null) { @@ -2101,15 +2117,26 @@ public class MessagingController implements Runnable { if (account.getErrorFolderName().equals(srcFolder)) { return; } + final ArrayList remoteUids = new ArrayList(); + for (String uid : uids) { + // ignore unsynced messages + if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { + remoteUids.add(uid); + } + } + if (remoteUids.size() == 0) { + return; + } PendingCommand command = new PendingCommand(); command.command = PENDING_COMMAND_MOVE_OR_COPY_BULK; - int length = 3 + uids.length; + int length = 3 + remoteUids.size(); command.arguments = new String[length]; command.arguments[0] = srcFolder; command.arguments[1] = destFolder; command.arguments[2] = Boolean.toString(isCopy); - System.arraycopy(uids, 0, command.arguments, 3, uids.length); + System.arraycopy(remoteUids.toArray(EMPTY_STRING_ARRAY), 0, command.arguments, 3, + remoteUids.size()); queuePendingCommand(account, command); } /** @@ -2222,13 +2249,13 @@ public class MessagingController implements Runnable { remoteFolder = remoteStore.getFolder(folder); if (!remoteFolder.exists()) { if (!remoteFolder.create()) { - // ASH log something? + Log.w(K9.LOG_TAG, "updateUids: Cannot create remote folder " + folder); return; } } remoteFolder.open(OpenMode.READ_WRITE); if (remoteFolder.getMode() != OpenMode.READ_WRITE) { - // ASH log something? + Log.w(K9.LOG_TAG, "updateUids: Cannot open remote folder " + folder); return; } @@ -2247,12 +2274,12 @@ public class MessagingController implements Runnable { for (MessagingListener l : getListeners()) { l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); } - } else { + }/* else { Log.w(K9.LOG_TAG, "No remote message with message-id found, appending instead."); /* * If the message does not exist remotely we just upload it and then * update our local copy with the new uid. - */ + *//* FetchProfile fp = new FetchProfile(); fp.add(FetchProfile.Item.BODY); localFolder.fetch(new Message[] { localMessage } , fp, null); @@ -2264,7 +2291,7 @@ public class MessagingController implements Runnable { for (MessagingListener l : getListeners()) { l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); } - } + }*/ } } finally { closeFolder(localFolder); @@ -2272,19 +2299,47 @@ public class MessagingController implements Runnable { } } + public void localizeUids(LocalFolder folder) throws MessagingException { + Message[] messages = folder.getMessages(null); + for (Message message : messages) { + String oldUid = message.getUid(); + Log.d("ASH", "old UID = " + oldUid); + if (!oldUid.startsWith(K9.LOCAL_UID_PREFIX)) { + String newUid = K9.LOCAL_UID_PREFIX + java.util.UUID.randomUUID().toString(); + Log.d("ASH", "new UID = " + newUid); + message.setUid(newUid); + folder.changeUid((LocalMessage)message); + + for (MessagingListener l : getListeners()) { + l.messageUidChanged(folder.getAccount(), folder.getName(), oldUid, newUid); + } + } + } + } private void queueSetFlag(final Account account, final String folderName, final String newState, final String flag, final String[] uids) { + final ArrayList remoteUids = new ArrayList(); + for (String uid : uids) { + // ignore unsynced messages + if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { + remoteUids.add(uid); + } + } + if (remoteUids.size() == 0) { + return; + } putBackground("queueSetFlag " + account.getDescription() + ":" + folderName, null, new Runnable() { @Override public void run() { PendingCommand command = new PendingCommand(); command.command = PENDING_COMMAND_SET_FLAG_BULK; - int length = 3 + uids.length; + int length = 3 + remoteUids.size(); command.arguments = new String[length]; command.arguments[0] = folderName; command.arguments[1] = newState; command.arguments[2] = flag; - System.arraycopy(uids, 0, command.arguments, 3, uids.length); + System.arraycopy(remoteUids.toArray(EMPTY_STRING_ARRAY), 0, command.arguments, 3, + remoteUids.size()); queuePendingCommand(account, command); processPendingCommands(account); } @@ -2375,7 +2430,9 @@ public class MessagingController implements Runnable { closeFolder(remoteFolder); } } + private void queueExpunge(final Account account, final String folderName) { + // ASH if folder.isLocalOnly() return ? putBackground("queueExpunge " + account.getDescription() + ":" + folderName, null, new Runnable() { @Override public void run() { @@ -2390,6 +2447,7 @@ public class MessagingController implements Runnable { } }); } + private void processPendingExpunge(PendingCommand command, Account account) throws MessagingException { String folder = command.arguments[0]; @@ -2445,6 +2503,10 @@ public class MessagingController implements Runnable { return; } + if (localFolder.isLocalOnly()) { + return; + } + Store remoteStore = account.getRemoteStore(); remoteFolder = remoteStore.getFolder(folder); @@ -2576,9 +2638,9 @@ public class MessagingController implements Runnable { final boolean newState) { // TODO: put this into the background, but right now that causes odd behavior // because the FolderMessageList doesn't have its own cache of the flag states - Folder localFolder = null; + LocalFolder localFolder = null; try { - Store localStore = account.getLocalStore(); + LocalStore localStore = account.getLocalStore(); localFolder = localStore.getFolder(folderName); localFolder.open(OpenMode.READ_WRITE); ArrayList messages = new ArrayList(); @@ -2606,6 +2668,10 @@ public class MessagingController implements Runnable { return; } + if (localFolder.isLocalOnly()) { + return; + } + queueSetFlag(account, folderName, Boolean.toString(newState), flag.toString(), uids); processPendingCommands(account); } catch (MessagingException me) { @@ -2633,75 +2699,95 @@ public class MessagingController implements Runnable { put("loadMessageForViewRemote", listener, new Runnable() { @Override public void run() { - Folder remoteFolder = null; - LocalFolder localFolder = null; - try { - LocalStore localStore = account.getLocalStore(); - localFolder = localStore.getFolder(folder); - localFolder.open(OpenMode.READ_WRITE); - - Message message = localFolder.getMessage(uid); - - if (message.isSet(Flag.X_DOWNLOADED_FULL)) { - /* - * If the message has been synchronized since we were called we'll - * just hand it back cause it's ready to go. - */ - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.ENVELOPE); - fp.add(FetchProfile.Item.BODY); - localFolder.fetch(new Message[] { message }, fp, null); - } else { - /* - * At this point the message is not available, so we need to download it - * fully if possible. - */ - - Store remoteStore = account.getRemoteStore(); - remoteFolder = remoteStore.getFolder(folder); - remoteFolder.open(OpenMode.READ_WRITE); - - // Get the remote message and fully download it - Message remoteMessage = remoteFolder.getMessage(uid); - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.BODY); - remoteFolder.fetch(new Message[] { remoteMessage }, fp, null); - - // Store the message locally and load the stored message into memory - localFolder.appendMessages(new Message[] { remoteMessage }); - fp.add(FetchProfile.Item.ENVELOPE); - message = localFolder.getMessage(uid); - localFolder.fetch(new Message[] { message }, fp, null); - - // Mark that this message is now fully synched - message.setFlag(Flag.X_DOWNLOADED_FULL, true); - } - - // now that we have the full message, refresh the headers - for (MessagingListener l : getListeners(listener)) { - l.loadMessageForViewHeadersAvailable(account, folder, uid, message); - } - - for (MessagingListener l : getListeners(listener)) { - l.loadMessageForViewBodyAvailable(account, folder, uid, message); - } - for (MessagingListener l : getListeners(listener)) { - l.loadMessageForViewFinished(account, folder, uid, message); - } - } catch (Exception e) { - for (MessagingListener l : getListeners(listener)) { - l.loadMessageForViewFailed(account, folder, uid, e); - } - addErrorMessage(account, null, e); - - } finally { - closeFolder(remoteFolder); - closeFolder(localFolder); - } - }//run + loadMessageForViewRemoteSynchronous(account, folder, uid, listener); + } }); } + public boolean loadMessageForViewRemoteSynchronous(final Account account, final String folder, + final String uid, final MessagingListener listener) { + Folder remoteFolder = null; + LocalFolder localFolder = null; + try { + LocalStore localStore = account.getLocalStore(); + localFolder = localStore.getFolder(folder); + localFolder.open(OpenMode.READ_WRITE); + + Message message = localFolder.getMessage(uid); + + if (uid.startsWith(K9.LOCAL_UID_PREFIX)) { + Log.w(K9.LOG_TAG, "Message has local UID so cannot download fully."); + Toast.makeText(mApplication, "Message has local UID so cannot download fully", + Toast.LENGTH_LONG).show(); + message.setFlag(Flag.X_DOWNLOADED_FULL, true); + message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); + } else if (localFolder.isLocalOnly()) { + Log.w(K9.LOG_TAG, "Message in local-only folder so cannot download fully."); + Toast.makeText(mApplication, "Message in local-only folder so cannot download fully", + Toast.LENGTH_LONG).show(); + message.setFlag(Flag.X_DOWNLOADED_FULL, true); + message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); + } + + if (message.isSet(Flag.X_DOWNLOADED_FULL)) { + /* + * If the message has been synchronized since we were called we'll + * just hand it back cause it's ready to go. + */ + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.ENVELOPE); + fp.add(FetchProfile.Item.BODY); + localFolder.fetch(new Message[] { message }, fp, null); + } else { + /* + * At this point the message is not available, so we need to download it + * fully if possible. + */ + + Store remoteStore = account.getRemoteStore(); + remoteFolder = remoteStore.getFolder(folder); + remoteFolder.open(OpenMode.READ_WRITE); + + // Get the remote message and fully download it + Message remoteMessage = remoteFolder.getMessage(uid); + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.BODY); + remoteFolder.fetch(new Message[] { remoteMessage }, fp, null); + + // Store the message locally and load the stored message into memory + localFolder.appendMessages(new Message[] { remoteMessage }); + fp.add(FetchProfile.Item.ENVELOPE); + message = localFolder.getMessage(uid); + localFolder.fetch(new Message[] { message }, fp, null); + + // Mark that this message is now fully synched + message.setFlag(Flag.X_DOWNLOADED_FULL, true); + } + + // now that we have the full message, refresh the headers + for (MessagingListener l : getListeners(listener)) { + l.loadMessageForViewHeadersAvailable(account, folder, uid, message); + } + + for (MessagingListener l : getListeners(listener)) { + l.loadMessageForViewBodyAvailable(account, folder, uid, message); + } + for (MessagingListener l : getListeners(listener)) { + l.loadMessageForViewFinished(account, folder, uid, message); + } + return true; + } catch (Exception e) { + for (MessagingListener l : getListeners(listener)) { + l.loadMessageForViewFailed(account, folder, uid, e); + } + addErrorMessage(account, null, e); + return false; + } finally { + closeFolder(remoteFolder); + closeFolder(localFolder); + } + } + public void loadMessageForView(final Account account, final String folder, final String uid, final MessagingListener listener) { for (MessagingListener l : getListeners(listener)) { @@ -3105,11 +3191,13 @@ public class MessagingController implements Runnable { if (K9.DEBUG) Log.i(K9.LOG_TAG, "Moved sent message to folder '" + account.getSentFolderName() + "' (" + localSentFolder.getId() + ") "); - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_APPEND; - command.arguments = new String[] { localSentFolder.getName(), message.getUid() }; - queuePendingCommand(account, command); - processPendingCommands(account); + if (!localSentFolder.isLocalOnly()) { + PendingCommand command = new PendingCommand(); + command.command = PENDING_COMMAND_APPEND; + command.arguments = new String[] { localSentFolder.getName(), message.getUid() }; + queuePendingCommand(account, command); + processPendingCommands(account); + } } } catch (Exception e) { @@ -3243,61 +3331,105 @@ public class MessagingController implements Runnable { Store remoteStore = account.getRemoteStore(); LocalFolder localSrcFolder = localStore.getFolder(srcFolder); LocalFolder localDestFolder = localStore.getFolder(destFolder); - List uids = new LinkedList(); List localUids = new LinkedList(); + + localSrcFolder.open(OpenMode.READ_WRITE); + boolean needToLocalizeSourceFolder = false; + for (Message message : inMessages) { String uid = message.getUid(); - // ASH fixme: add all messages, and later handle local ones separately? - if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { + // ASH instead, add all messages, and later handle local ones separately? + if (!uid.startsWith(K9.LOCAL_UID_PREFIX) && !localSrcFolder.isLocalOnly()) { uids.add(uid); } else { localUids.add(uid); + if (isCopy && !uid.startsWith(K9.LOCAL_UID_PREFIX)) { + needToLocalizeSourceFolder = true; + } } } Message[] messages = localSrcFolder.getMessages(uids.toArray(EMPTY_STRING_ARRAY), null); if (messages.length > 0) { + localDestFolder.open(OpenMode.READ_WRITE); + boolean checkForPartialDownload = (!localSrcFolder.isLocalOnly() && + localDestFolder.isLocalOnly()) ? true : false; + Map origUidMap = new HashMap(); for (Message message : messages) { - origUidMap.put(message.getUid(), message); - } - - if (K9.DEBUG) { - Log.i(K9.LOG_TAG, "moveOrCopyMessageSynchronous: source folder = " + srcFolder - + ", " + messages.length + " messages, " + ", destination folder = " + - destFolder + ", isCopy = " + isCopy); - } - - if (isCopy) { - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.ENVELOPE); - fp.add(FetchProfile.Item.BODY); - localSrcFolder.fetch(messages, fp, null); - localSrcFolder.copyMessages(messages, localDestFolder); - } else { - localSrcFolder.moveMessages(messages, localDestFolder); - for (Map.Entry entry : origUidMap.entrySet()) { - String origUid = entry.getKey(); - Message message = entry.getValue(); - for (MessagingListener l : getListeners()) { - l.messageUidChanged(account, srcFolder, origUid, message.getUid()); + if (checkForPartialDownload && message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { + // fully download message if it's moved/coied to a local-only folder + Log.d("ASH", "downloading message..."); + if (loadMessageForViewRemoteSynchronous(account, srcFolder, + message.getUid(), listener)) { + Log.d("ASH", "downloaded message"); + message.setFlag(Flag.X_DOWNLOADED_FULL, true); + message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); + origUidMap.put(message.getUid(), message); + } else { + Log.e(K9.LOG_TAG, "Cannot download message " + message.getUid() + + " in folder " + srcFolder + " -- skipping it for move/copy."); + Toast.makeText(mApplication, "Cannot download message " + + message.getSubject() + " in folder " + srcFolder + + " -- skipping it for move/copy.", Toast.LENGTH_LONG).show(); } - unsuppressMessage(account, srcFolder, origUid); + } else { + origUidMap.put(message.getUid(), message); } } - if (!localDestFolder.isLocalOnly() && ((!isCopy && remoteStore.isMoveCapable()) || - (isCopy && remoteStore.isCopyCapable()))) { - queueMoveOrCopy(account, srcFolder, destFolder, isCopy, - origUidMap.keySet().toArray(EMPTY_STRING_ARRAY)); - } else if (!isCopy && !localSrcFolder.isLocalOnly()) { - queueSetFlag(account, srcFolder, Boolean.toString(true), - Flag.DELETED.toString(), - origUidMap.keySet().toArray(EMPTY_STRING_ARRAY)); - if (Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) { - queueExpunge(account, srcFolder); + if (checkForPartialDownload) { + messages = origUidMap.values().toArray(EMPTY_MESSAGE_ARRAY); + } + if (messages.length > 0) { + if (K9.DEBUG) { + Log.i(K9.LOG_TAG, "moveOrCopyMessageSynchronous: source folder = " + srcFolder + + ", " + messages.length + " messages, " + ", destination folder = " + + destFolder + ", isCopy = " + isCopy); + } + + if (isCopy) { + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.ENVELOPE); + fp.add(FetchProfile.Item.BODY); + localSrcFolder.fetch(messages, fp, null); + localSrcFolder.copyMessages(messages, localDestFolder); + } else { + localSrcFolder.moveMessages(messages, localDestFolder); + for (Map.Entry entry : origUidMap.entrySet()) { + String origUid = entry.getKey(); + Message message = entry.getValue(); + for (MessagingListener l : getListeners()) { + l.messageUidChanged(account, srcFolder, origUid, message.getUid()); + } + unsuppressMessage(account, srcFolder, origUid); + } + } + + if (!localDestFolder.isLocalOnly() && + (isCopy ? remoteStore.isCopyCapable() : remoteStore.isMoveCapable())) { + // synced message copy/move to remote folder + queueMoveOrCopy(account, srcFolder, destFolder, isCopy, + origUidMap.keySet().toArray(EMPTY_STRING_ARRAY)); + } else if (!isCopy && !localSrcFolder.isLocalOnly()) { + // synced message move to local folder: delete from source folder ASH maybe a better way. + if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) { + queueSetFlag(account, srcFolder, Boolean.toString(true), + Flag.DELETED.toString(), + origUidMap.keySet().toArray(EMPTY_STRING_ARRAY)); + if (Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) { + queueExpunge(account, srcFolder); + } + } else if (account.getDeletePolicy() == Account.DELETE_POLICY_MARK_AS_READ) { + queueSetFlag(account, srcFolder, Boolean.toString(true), + Flag.SEEN.toString(), + origUidMap.keySet().toArray(EMPTY_STRING_ARRAY)); + } else { + if (K9.DEBUG) + Log.d(K9.LOG_TAG, "Delete policy " + account.getDeletePolicy() + " prevents delete from server"); + } } } } @@ -3334,17 +3466,22 @@ public class MessagingController implements Runnable { fp.add(FetchProfile.Item.BODY); localSrcFolder.fetch(localMessages, fp, null); - if ((!localDestFolder.isLocalOnly()) && ((!isCopy && (remoteStore.isMoveCapable())) - || (isCopy && (remoteStore.isCopyCapable())))) { + if (account.isAutoUploadOnMove() && !localDestFolder.isLocalOnly() && + (isCopy ? remoteStore.isCopyCapable() : remoteStore.isMoveCapable())) { + // local message copy/move to remote folder for (Message message : localMessages) { saveMessage(account, message, destFolder); } } else if (isCopy) { + // local message copy to local folder localSrcFolder.copyMessages(localMessages, localDestFolder); } } processPendingCommands(account); + if (needToLocalizeSourceFolder) { + localizeUids(localSrcFolder); + } } catch (UnavailableStorageException e) { Log.i(K9.LOG_TAG, "Failed to move/copy message because storage is not available - trying again later."); throw new UnavailableAccountException(e); @@ -3406,10 +3543,10 @@ public class MessagingController implements Runnable { } - private void deleteMessagesSynchronous(final Account account, final String folder, final Message[] messages, + private void deleteMessagesSynchronous(final Account account, final String folder, Message[] messages, MessagingListener listener) { - Folder localFolder = null; - Folder localTrashFolder = null; + LocalFolder localFolder = null; + LocalFolder localTrashFolder = null; String[] uids = getUidsFromMessages(messages); try { //We need to make these callbacks before moving the messages to the trash @@ -3419,7 +3556,7 @@ public class MessagingController implements Runnable { l.messageDeleted(account, folder, message); } } - Store localStore = account.getLocalStore(); + LocalStore localStore = account.getLocalStore(); localFolder = localStore.getFolder(folder); if (folder.equals(account.getTrashFolderName()) || K9.FOLDER_NONE.equals(account.getTrashFolderName())) { if (K9.DEBUG) @@ -3429,12 +3566,54 @@ public class MessagingController implements Runnable { } else { localTrashFolder = localStore.getFolder(account.getTrashFolderName()); if (!localTrashFolder.exists()) { - localTrashFolder.create(); + if (account.getRemoteStore().isMoveCapable()) { + localTrashFolder.create(); + } else { + localTrashFolder.create(true); + } } if (localTrashFolder.exists()) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "Deleting messages in normal folder, moving"); + localTrashFolder.open(OpenMode.READ_WRITE); + // download messages first if trash folder is local-only, + // otherwise it's not undoable. + if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE && + localTrashFolder.isLocalOnly() && !localFolder.isLocalOnly()) { + Map origUidMap = new HashMap(); + Map skipUidMap = new HashMap(); + for (Message message : messages) { + if (message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { + Log.d("ASH", "downloading message..."); + if (loadMessageForViewRemoteSynchronous(account, folder, + message.getUid(), listener)) { + Log.d("ASH", "downloaded message"); + message.setFlag(Flag.X_DOWNLOADED_FULL, true); + message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); + origUidMap.put(message.getUid(), message); + } else { + skipUidMap.put(message.getUid(), message); + Log.e(K9.LOG_TAG, "Cannot download message -- skipping it for move to trash."); + Toast.makeText(mApplication, "Cannot download message " + + message.getSubject() + " in folder " + folder + + " -- skipping it for move to trash.", + Toast.LENGTH_LONG).show(); + } + } else { + origUidMap.put(message.getUid(), message); + } + } + messages = origUidMap.values().toArray(EMPTY_MESSAGE_ARRAY); + uids = origUidMap.keySet().toArray(EMPTY_STRING_ARRAY); + for (String uid : skipUidMap.keySet()) { + unsuppressMessage(account, folder, uid); + } + if (messages.length == 0) { + Log.w(K9.LOG_TAG, "Not deleting any messages."); + return; + } + } localFolder.moveMessages(messages, localTrashFolder); } @@ -3451,32 +3630,37 @@ public class MessagingController implements Runnable { Log.d(K9.LOG_TAG, "Delete policy for account " + account.getDescription() + " is " + account.getDeletePolicy()); if (folder.equals(account.getOutboxFolderName())) { - for (Message message : messages) { - // If the message was in the Outbox, then it has been copied to local Trash, and has - // to be copied to remote trash - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_APPEND; - command.arguments = - new String[] { - account.getTrashFolderName(), - message.getUid() - }; - queuePendingCommand(account, command); + localTrashFolder = (LocalFolder)localStore.getFolder(account.getTrashFolderName()); + localTrashFolder.open(OpenMode.READ_WRITE); + if (!localTrashFolder.isLocalOnly()) { + for (Message message : messages) { + // If the message was in the Outbox, then it has been copied to local Trash, and has + // to be copied to remote trash + PendingCommand command = new PendingCommand(); + command.command = PENDING_COMMAND_APPEND; + command.arguments = new String[] { account.getTrashFolderName(), + message.getUid() }; + queuePendingCommand(account, command); + } + processPendingCommands(account); } - processPendingCommands(account); - } else if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) { - if (folder.equals(account.getTrashFolderName())) { - queueSetFlag(account, folder, Boolean.toString(true), Flag.DELETED.toString(), uids); + } else if (!localFolder.isLocalOnly()) { + localTrashFolder = (LocalFolder)localStore.getFolder(account.getTrashFolderName()); + localTrashFolder.open(OpenMode.READ_WRITE); + if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) { + if (folder.equals(account.getTrashFolderName()) || localTrashFolder.isLocalOnly()) { + queueSetFlag(account, folder, Boolean.toString(true), Flag.DELETED.toString(), uids); + } else { + queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids); + } + processPendingCommands(account); + } else if (account.getDeletePolicy() == Account.DELETE_POLICY_MARK_AS_READ) { + queueSetFlag(account, folder, Boolean.toString(true), Flag.SEEN.toString(), uids); + processPendingCommands(account); } else { - queueMoveOrCopy(account, folder, account.getTrashFolderName(), false, uids); + if (K9.DEBUG) + Log.d(K9.LOG_TAG, "Delete policy " + account.getDeletePolicy() + " prevents delete from server"); } - processPendingCommands(account); - } else if (account.getDeletePolicy() == Account.DELETE_POLICY_MARK_AS_READ) { - queueSetFlag(account, folder, Boolean.toString(true), Flag.SEEN.toString(), uids); - processPendingCommands(account); - } else { - if (K9.DEBUG) - Log.d(K9.LOG_TAG, "Delete policy " + account.getDeletePolicy() + " prevents delete from server"); } for (String uid : uids) { unsuppressMessage(account, folder, uid); @@ -3532,8 +3716,8 @@ public class MessagingController implements Runnable { public void run() { LocalFolder localFolder = null; try { - Store localStore = account.getLocalStore(); - localFolder = (LocalFolder) localStore.getFolder(account.getTrashFolderName()); + LocalStore localStore = account.getLocalStore(); + localFolder = localStore.getFolder(account.getTrashFolderName()); localFolder.open(OpenMode.READ_WRITE); localFolder.setFlags(new Flag[] { Flag.DELETED }, true); localFolder.resetUnreadAndFlaggedCounts(); @@ -3541,12 +3725,14 @@ public class MessagingController implements Runnable { for (MessagingListener l : getListeners()) { l.emptyTrashCompleted(account); } - List args = new ArrayList(); - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_EMPTY_TRASH; - command.arguments = args.toArray(EMPTY_STRING_ARRAY); - queuePendingCommand(account, command); - processPendingCommands(account); + if (!localFolder.isLocalOnly()) { + List args = new ArrayList(); + PendingCommand command = new PendingCommand(); + command.command = PENDING_COMMAND_EMPTY_TRASH; + command.arguments = args.toArray(EMPTY_STRING_ARRAY); + queuePendingCommand(account, command); + processPendingCommands(account); + } // ASH else do we need to destroy local messages? cketti says yes. } catch (UnavailableStorageException e) { Log.i(K9.LOG_TAG, "Failed to empty trash because storage is not available - trying again later."); throw new UnavailableAccountException(e); @@ -3724,11 +3910,16 @@ public class MessagingController implements Runnable { Account.FolderMode aDisplayMode = account.getFolderDisplayMode(); Account.FolderMode aSyncMode = account.getFolderSyncMode(); - Store localStore = account.getLocalStore(); - for (final Folder folder : localStore.getPersonalNamespaces(false)) { + LocalStore localStore = account.getLocalStore(); + for (final LocalFolder folder : localStore.getPersonalNamespaces(false)) { folder.open(Folder.OpenMode.READ_WRITE); folder.refresh(prefs); + if (folder.isLocalOnly()) { + // Never sync a folder that is marked as local-only. + continue; + } + Folder.FolderClass fDisplayClass = folder.getDisplayClass(); Folder.FolderClass fSyncClass = folder.getSyncClass(); @@ -4171,14 +4362,13 @@ public class MessagingController implements Runnable { localMessage = localFolder.getMessage(message.getUid()); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); - PendingCommand command = new PendingCommand(); - command.command = PENDING_COMMAND_APPEND; - command.arguments = new String[] { - localFolder.getName(), - localMessage.getUid() - }; - queuePendingCommand(account, command); - processPendingCommands(account); + if (!localFolder.isLocalOnly()) { + PendingCommand command = new PendingCommand(); + command.command = PENDING_COMMAND_APPEND; + command.arguments = new String[] { localFolder.getName(), localMessage.getUid() }; + queuePendingCommand(account, command); + processPendingCommands(account); + } } catch (MessagingException e) { Log.e(K9.LOG_TAG, "Unable to save message to " + folderName + ".", e); @@ -4288,8 +4478,8 @@ public class MessagingController implements Runnable { List names = new ArrayList(); - Store localStore = account.getLocalStore(); - for (final Folder folder : localStore.getPersonalNamespaces(false)) { + LocalStore localStore = account.getLocalStore(); + for (final LocalFolder folder : localStore.getPersonalNamespaces(false)) { if (folder.getName().equals(account.getErrorFolderName()) || folder.getName().equals(account.getOutboxFolderName())) { /* @@ -4303,6 +4493,11 @@ public class MessagingController implements Runnable { folder.open(Folder.OpenMode.READ_WRITE); folder.refresh(prefs); + if (folder.isLocalOnly()) { + // Never push a folder that is marked as local-only. + continue; + } + Folder.FolderClass fDisplayClass = folder.getDisplayClass(); Folder.FolderClass fPushClass = folder.getPushClass(); diff --git a/src/com/fsck/k9/mail/Folder.java b/src/com/fsck/k9/mail/Folder.java index 10ac982ad..037f83aad 100644 --- a/src/com/fsck/k9/mail/Folder.java +++ b/src/com/fsck/k9/mail/Folder.java @@ -104,11 +104,7 @@ public abstract class Folder { public void moveMessages(Message[] msgs, Folder folder) throws MessagingException {} - public void delete(Message[] msgs, String trashFolderName) throws MessagingException { -// ASH remove these two lines but keep the empty method -Log.e("ASH", "and i didn't think that Folder.delete(msgs, trashFolderName) ever got called: " + msgs[0].getClass()); -throw new MessagingException("ASH : and i didn't think that Folder.delete(msgs, trashFolderName) ever got called: " + msgs[0].getClass()); - } + public void delete(Message[] msgs, String trashFolderName) throws MessagingException {} public abstract void setFlags(Message[] messages, Flag[] flags, boolean value) throws MessagingException; diff --git a/src/com/fsck/k9/mail/Store.java b/src/com/fsck/k9/mail/Store.java index 5013a8f29..f0adaf63e 100644 --- a/src/com/fsck/k9/mail/Store.java +++ b/src/com/fsck/k9/mail/Store.java @@ -166,6 +166,10 @@ public abstract class Store { return false; } + public boolean isAppendCapable() { + return false; + } + public void sendMessages(Message[] messages) throws MessagingException { } diff --git a/src/com/fsck/k9/mail/store/ImapStore.java b/src/com/fsck/k9/mail/store/ImapStore.java index 6f5d15cdb..e857270e9 100644 --- a/src/com/fsck/k9/mail/store/ImapStore.java +++ b/src/com/fsck/k9/mail/store/ImapStore.java @@ -833,6 +833,10 @@ public class ImapStore extends Store { return true; } + @Override + public boolean isAppendCapable() { + return true; + } class ImapFolder extends Folder { private String mName; diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 357ed7acf..7485530f2 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -888,6 +888,11 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); return true; } + @Override + public boolean isAppendCapable() { + return true; + } + public Message[] searchForMessages(MessageRetrievalListener listener, String[] queryFields, String queryString, List folders, Message[] messages, final Flag[] requiredFlags, final Flag[] forbiddenFlags) throws MessagingException { @@ -1248,7 +1253,8 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); } } else { Log.w(K9.LOG_TAG, "Creating folder " + getName() + " with existing id " + getId()); - create(true); // ASH should this always be true? + create(!LocalStore.this.mAccount.getRemoteStore().isAppendCapable() && + !LocalStore.this.mAccount.getInboxFolderName().equals(getName())); open(mode); } } catch (MessagingException e) { @@ -2135,6 +2141,17 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly); appendMessages(messages, false); } + public void expunge() throws MessagingException { + List deletedMessages = new ArrayList(); + for (Message message : getMessages(null)) { + if (message.isSet(Flag.DELETED)) { + deletedMessages.add(message); + Log.d("ASH", "about to destroy " + message.getUid()); + } + } + destroyMessages(deletedMessages.toArray(EMPTY_MESSAGE_ARRAY)); + } + public void destroyMessages(final Message[] messages) throws MessagingException { try { database.execute(true, new DbCallback() { @@ -2709,14 +2726,14 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly); clearMessagesWhere(where, params); } - - public void clearAllMessages() throws MessagingException { - final String where = "folder_id = ?"; - final String[] params = new String[] { - Long.toString(mFolderId) - }; + clearAllMessages(true); + } + public void clearAllMessages(boolean includeLocalOnly) throws MessagingException { + final String where = "folder_id = ?" + (includeLocalOnly ? "" : + " AND uid NOT LIKE '" + K9.LOCAL_UID_PREFIX + "%'"); + final String[] params = new String[] { Long.toString(mFolderId) }; clearMessagesWhere(where, params); setPushState(null); diff --git a/src/com/fsck/k9/mail/store/WebDavStore.java b/src/com/fsck/k9/mail/store/WebDavStore.java index cdc5c9263..6818ac263 100644 --- a/src/com/fsck/k9/mail/store/WebDavStore.java +++ b/src/com/fsck/k9/mail/store/WebDavStore.java @@ -570,6 +570,11 @@ public class WebDavStore extends Store { return true; } + @Override + public boolean isAppendCapable() { + return true; + } + private String getSpecialFoldersList() { StringBuilder buffer = new StringBuilder(200); buffer.append(""); diff --git a/src/com/fsck/k9/preferences/GlobalSettings.java b/src/com/fsck/k9/preferences/GlobalSettings.java index 42ad9312d..5032b53f1 100644 --- a/src/com/fsck/k9/preferences/GlobalSettings.java +++ b/src/com/fsck/k9/preferences/GlobalSettings.java @@ -171,6 +171,9 @@ public class GlobalSettings { s.put("registeredNameColor", Settings.versions( new V(1, new ColorSetting(0xFF00008F)) )); + s.put("showAdvancedOptions", Settings.versions( + new V(1, new BooleanSetting(false)) + )); s.put("showContactName", Settings.versions( new V(1, new BooleanSetting(false)) )); diff --git a/src/com/fsck/k9/view/MessageHeader.java b/src/com/fsck/k9/view/MessageHeader.java index 371bc5905..992239116 100644 --- a/src/com/fsck/k9/view/MessageHeader.java +++ b/src/com/fsck/k9/view/MessageHeader.java @@ -45,7 +45,6 @@ public class MessageHeader extends LinearLayout { private View mChip; private CheckBox mFlagged; - private int defaultSubjectColor; private LinearLayout mToContainerView; private LinearLayout mCcContainerView; private TextView mAdditionalHeadersView; @@ -93,7 +92,6 @@ public class MessageHeader extends LinearLayout { mTimeView = (TextView) findViewById(R.id.time); mFlagged = (CheckBox) findViewById(R.id.flagged); - defaultSubjectColor = mSubjectView.getCurrentTextColor(); mSubjectView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewSubject()); mTimeView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewTime()); mDateView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mFontSizes.getMessageViewDate()); @@ -212,7 +210,11 @@ public class MessageHeader extends LinearLayout { } else { mSubjectView.setText(subject); } - mSubjectView.setTextColor(0xff000000 | defaultSubjectColor); + if (message.getUid().startsWith(K9.LOCAL_UID_PREFIX)) { + mSubjectView.setTextColor(mAccount.getChipColor()); + } else { + mSubjectView.setTextColor(0xff000000); + } mFromView.setText(from);