From c46afade5fca429cedc7a05848b47b5178e4f5ba Mon Sep 17 00:00:00 2001 From: ashley willis Date: Thu, 16 Feb 2012 17:09:42 -0600 Subject: [PATCH] reworked LocalFolder.isLocalOnly(): mLocalOnly is now Boolean instead of boolean so it can be uninitialized before the database sets it, and the folder doesn't need to be opened first. reworked LocalFolder.setLocalOnly() to convert messages to local-only (including full download) or create remote folder if needed. reworked MessagingController.localizeUids() to fully download if necessary. made trash folder local-only status depend on delete policy, and not able to be changed from folder settings. fixed LocalFolder.purgeToVisibleLimit() to no longer delete local-only messages. (long-standing bug) made processPendingAppend() (PENDING_COMMAND_APPEND) handle multiple emails and notify listeners that message has been updated. added code to create local-only folders on settings import. prevented user-created folder named /inbox/i. refactored onUpload() to put all logic in MessagingController.appendMessages(). removed MessagingController.saveMessage() and reverted MessagingController.saveDraft(). automatic account setup now sets delete policy to DELETE_POLICY_ON_DELETE on IMAP accounts like manual setup does. (long-standing issue) set default names for Archive and Spam on manual config. (long-standing issue) added code to save/delete folder settings on new or renamed folders. --- src/com/fsck/k9/Account.java | 51 ++- src/com/fsck/k9/activity/Accounts.java | 28 +- src/com/fsck/k9/activity/ChooseFolder.java | 3 + src/com/fsck/k9/activity/FolderList.java | 3 + src/com/fsck/k9/activity/MessageList.java | 27 +- src/com/fsck/k9/activity/MessageView.java | 12 +- .../k9/activity/setup/AccountSettings.java | 2 + .../k9/activity/setup/AccountSetupBasics.java | 12 + .../k9/activity/setup/FolderSettings.java | 26 +- .../k9/controller/MessagingController.java | 418 +++++++++++------- src/com/fsck/k9/mail/store/LocalStore.java | 97 +++- .../fsck/k9/preferences/AccountSettings.java | 3 + .../fsck/k9/preferences/FolderSettings.java | 3 + .../fsck/k9/preferences/SettingsImporter.java | 1 + 14 files changed, 458 insertions(+), 228 deletions(-) diff --git a/src/com/fsck/k9/Account.java b/src/com/fsck/k9/Account.java index f956390fb..4bd1e2c12 100644 --- a/src/com/fsck/k9/Account.java +++ b/src/com/fsck/k9/Account.java @@ -14,6 +14,7 @@ import com.fsck.k9.mail.Address; 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.StorageManager; import com.fsck.k9.mail.store.StorageManager.StorageProvider; import com.fsck.k9.view.ColorChip; @@ -51,7 +52,7 @@ public class Account implements BaseAccount { public static final String EXPUNGE_ON_POLL = "EXPUNGE_ON_POLL"; public static final int DELETE_POLICY_NEVER = 0; - public static final int DELETE_POLICY_7DAYS = 1; + //public static final int DELETE_POLICY_7DAYS = 1; public static final int DELETE_POLICY_ON_DELETE = 2; public static final int DELETE_POLICY_MARK_AS_READ = 3; @@ -82,7 +83,7 @@ public class Account implements BaseAccount { /** *
      * 0 - Never (DELETE_POLICY_NEVER)
-     * 1 - After 7 days (DELETE_POLICY_7DAYS)
+     * 1 - After 7 days (DELETE_POLICY_7DAYS) -- unused and commented out.
      * 2 - When I delete from inbox (DELETE_POLICY_ON_DELETE)
      * 3 - Mark as read (DELETE_POLICY_MARK_AS_READ)
      * 
@@ -902,7 +903,29 @@ public class Account implements BaseAccount { } public synchronized void setDeletePolicy(int deletePolicy) { - this.mDeletePolicy = deletePolicy; + try { + if (deletePolicy != mDeletePolicy && !K9.FOLDER_NONE.equals(mTrashFolderName) && + mTrashFolderName != null && getRemoteStore().isMoveCapable()) { + LocalFolder folder = getLocalStore().getFolder(mTrashFolderName); + if (folder.exists()) { + Log.d("ASH", "setDeletePolicy() 3"); + if (!folder.setLocalOnly(deletePolicy != DELETE_POLICY_ON_DELETE)) { + Log.d("ASH", "Cannot currently change delete policy."); + return; + } + } + } + this.mDeletePolicy = deletePolicy; + } catch (MessagingException e) { + if (e.getCause() instanceof java.net.UnknownHostException) { + Log.e(K9.LOG_TAG, "Cannot currently change delete policy due to unknown host: " + + e.getCause().getMessage()); + // ASH make toast + } else { + Log.e(K9.LOG_TAG, "Cannot currently change delete policy for unknown reason.", e); + // ASH make toast + } + } } @@ -949,7 +972,27 @@ public class Account implements BaseAccount { } public synchronized void setTrashFolderName(String trashFolderName) { - mTrashFolderName = trashFolderName; +Log.d("ASH", "Attempting to set trash folder name to " + trashFolderName); + if (trashFolderName == null || mStoreUri.startsWith("placeholder")) { +Log.d("ASH", "No change to trash folder name."); + return; + } else if (trashFolderName.equals(mTrashFolderName)) { +Log.d("ASH", "No change to trash folder name"); + return; + } + try { + if (!K9.FOLDER_NONE.equals(trashFolderName) && getRemoteStore().isMoveCapable()) { + LocalFolder folder = getLocalStore().getFolder(trashFolderName); +Log.d("ASH", "got local trash folder"); + if (folder.exists()) { +Log.d("ASH", "setTrashFolderName() attempting change of folder.setLocalOnly()"); + folder.setLocalOnly(mDeletePolicy != DELETE_POLICY_ON_DELETE); + } + } + mTrashFolderName = trashFolderName; + } catch (MessagingException e) { + Log.e(K9.LOG_TAG, "Cannot access store: ", e); + } } public synchronized String getArchiveFolderName() { diff --git a/src/com/fsck/k9/activity/Accounts.java b/src/com/fsck/k9/activity/Accounts.java index 70206b9b9..51224e34d 100644 --- a/src/com/fsck/k9/activity/Accounts.java +++ b/src/com/fsck/k9/activity/Accounts.java @@ -12,6 +12,8 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import android.app.Activity; import android.app.AlertDialog; @@ -92,7 +94,7 @@ import com.fsck.k9.preferences.SettingsImporter.AccountDescription; import com.fsck.k9.preferences.SettingsImporter.AccountDescriptionPair; import com.fsck.k9.preferences.SettingsImporter.ImportContents; import com.fsck.k9.preferences.SettingsImporter.ImportResults; - +import com.fsck.k9.preferences.Storage; public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener { @@ -109,6 +111,7 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC private static final int DIALOG_REMOVE_ACCOUNT = 1; private static final int DIALOG_CLEAR_ACCOUNT = 2; private static final int DIALOG_RECREATE_ACCOUNT = 3; + private static final String TRUE = "true"; private ConcurrentHashMap accountStats = new ConcurrentHashMap(); private ConcurrentHashMap pendingWork = new ConcurrentHashMap(); @@ -864,8 +867,31 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC // Start services if necessary K9.setServicesEnabled(mContext); + // Create local-only folders. + // ASH TODO reject certain names for IMAP + // ASH test on importanting an acct with about 200 remote folders. what situations execute this code? change of password? + Map storageMap = Storage.getStorage(mContext).getAll(); + Pattern pattern = Pattern.compile(mAccount.getUuid() + "\\..+\\.isLocalOnly"); + int substringStart = mAccount.getUuid().length() + 1; + for (String key : storageMap.keySet()) { + Matcher matcher = pattern.matcher(key); + if (matcher.find() && TRUE.equals(storageMap.get(key))) { + String folderName = key.substring(substringStart, key.length() - 12); + if (folderName.toUpperCase().matches(Account.INBOX)) { + Log.w(K9.LOG_TAG, "Skipping import of local-only INBOX. It should always be remote."); + continue; + } + if (mAccount.getLocalStore().createFolder(folderName, true)) { + Log.i(K9.LOG_TAG, "Created local-only folder '" + folderName + "'"); + } else { + Log.w(K9.LOG_TAG, "Failed to create local-only folder '" + folderName + "'"); + } + } + } + // Get list of folders from remote server MessagingController.getInstance(mApplication).listFolders(mAccount, true, null); + } catch (Exception e) { Log.e(K9.LOG_TAG, "Something went while setting account passwords", e); } diff --git a/src/com/fsck/k9/activity/ChooseFolder.java b/src/com/fsck/k9/activity/ChooseFolder.java index 57d42f2c0..060104e45 100644 --- a/src/com/fsck/k9/activity/ChooseFolder.java +++ b/src/com/fsck/k9/activity/ChooseFolder.java @@ -310,6 +310,9 @@ public class ChooseFolder extends K9ListActivity { if (folderName.matches("")) { Toast.makeText(getApplication(), "Folder name not given!", Toast.LENGTH_LONG).show(); return; + } else if (folderName.toUpperCase().matches(Account.INBOX)) { + Toast.makeText(getApplication(), "Refuse to create a folder named INBOX!", Toast.LENGTH_LONG).show(); + return; } try { Store store = mAccount.getRemoteStore(); diff --git a/src/com/fsck/k9/activity/FolderList.java b/src/com/fsck/k9/activity/FolderList.java index 1786f7dd4..ef1907752 100644 --- a/src/com/fsck/k9/activity/FolderList.java +++ b/src/com/fsck/k9/activity/FolderList.java @@ -494,6 +494,9 @@ public class FolderList extends K9ListActivity { if (folderName.matches("")) { Toast.makeText(getApplication(), "Folder name not given!", Toast.LENGTH_LONG).show(); return; + } else if (folderName.toUpperCase().matches(Account.INBOX)) { + Toast.makeText(getApplication(), "Refuse to create a folder named INBOX!", Toast.LENGTH_LONG).show(); + return; } try { Store store = mAccount.getRemoteStore(); diff --git a/src/com/fsck/k9/activity/MessageList.java b/src/com/fsck/k9/activity/MessageList.java index c54966acc..81ca1630e 100644 --- a/src/com/fsck/k9/activity/MessageList.java +++ b/src/com/fsck/k9/activity/MessageList.java @@ -729,22 +729,12 @@ public class MessageList mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount); } - // 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); - } + mLocalOnly = folder.isLocalOnly(); } 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 @@ -2316,6 +2306,7 @@ 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); + // ASH funky bug is here? if (message.uid.startsWith(K9.LOCAL_UID_PREFIX)) { view.setBackgroundColor(message.message.getFolder().getAccount().getChipColor()); view.getBackground().setAlpha(31); @@ -2730,19 +2721,7 @@ public class MessageList 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()); - } - } + mController.appendMessages(mAccount, holders); } /** diff --git a/src/com/fsck/k9/activity/MessageView.java b/src/com/fsck/k9/activity/MessageView.java index 86b9ee6ec..7f4256131 100644 --- a/src/com/fsck/k9/activity/MessageView.java +++ b/src/com/fsck/k9/activity/MessageView.java @@ -753,17 +753,7 @@ public class MessageView extends K9Activity implements OnClickListener { } 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()); - } + mController.appendMessages(mAccount, new Message[] { mMessage }, mMessageReference.folderName); } @Override diff --git a/src/com/fsck/k9/activity/setup/AccountSettings.java b/src/com/fsck/k9/activity/setup/AccountSettings.java index 4dbce46dc..7beaf4407 100644 --- a/src/com/fsck/k9/activity/setup/AccountSettings.java +++ b/src/com/fsck/k9/activity/setup/AccountSettings.java @@ -726,7 +726,9 @@ public class AccountSettings extends K9PreferenceActivity { mAccount.setGoToUnreadMessageSearch(mNotificationOpensUnread.isChecked()); mAccount.setNotificationShowsUnreadCount(mNotificationUnreadCount.isChecked()); mAccount.setFolderTargetMode(Account.FolderMode.valueOf(mTargetMode.getValue())); +Log.d("ASH", "Setting delete policy to " + mDeletePolicy.getValue()); mAccount.setDeletePolicy(Integer.parseInt(mDeletePolicy.getValue())); +Log.d("ASH", "Have set delete policy to " + mAccount.getDeletePolicy()); if (mIsExpungeCapable) { mAccount.setExpungePolicy(mExpungePolicy.getValue()); } diff --git a/src/com/fsck/k9/activity/setup/AccountSetupBasics.java b/src/com/fsck/k9/activity/setup/AccountSetupBasics.java index 27a3781dd..dea801ed1 100644 --- a/src/com/fsck/k9/activity/setup/AccountSetupBasics.java +++ b/src/com/fsck/k9/activity/setup/AccountSetupBasics.java @@ -232,6 +232,11 @@ public class AccountSetupBasics extends K9Activity mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam)); } mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); + if (incomingUri.toString().startsWith("imap")) { + mAccount.setDeletePolicy(Account.DELETE_POLICY_ON_DELETE); + } else if (incomingUri.toString().startsWith("pop3")) { + mAccount.setDeletePolicy(Account.DELETE_POLICY_NEVER); + } AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true); } catch (UnsupportedEncodingException enc) { // This really shouldn't happen since the encoding is hardcoded to UTF-8 @@ -310,6 +315,13 @@ public class AccountSetupBasics extends K9Activity mAccount.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts)); mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash)); mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); + mAccount.setArchiveFolderName(getString(R.string.special_mailbox_name_archive)); + // Yahoo! has a special folder for Spam, called "Bulk Mail". + if (domain.endsWith(".yahoo.com")) { + mAccount.setSpamFolderName("Bulk Mail"); + } else { + mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam)); + } AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked()); finish(); diff --git a/src/com/fsck/k9/activity/setup/FolderSettings.java b/src/com/fsck/k9/activity/setup/FolderSettings.java index 4f61c6034..7e00cc9c2 100644 --- a/src/com/fsck/k9/activity/setup/FolderSettings.java +++ b/src/com/fsck/k9/activity/setup/FolderSettings.java @@ -141,20 +141,24 @@ public class FolderSettings extends K9PreferenceActivity { mLocalOnly = (CheckBoxPreference)findPreference(PREFERENCE_LOCAL_ONLY); mLocalOnly.setChecked(mFolder.isLocalOnly()); if (store instanceof Pop3Store || mAccount.getInboxFolderName().equals(folderName) || - mAccount.getOutboxFolderName().equals(folderName)) { + mAccount.getOutboxFolderName().equals(folderName) || + mAccount.getTrashFolderName().equals(folderName)) { mLocalOnly.setEnabled(false); } - if (!K9.isShowAdvancedOptions()) {// ASH disabled for testing: || store instanceof Pop3Store) { + if (!K9.isShowAdvancedOptions() || store instanceof Pop3Store) { category.removePreference(mLocalOnly); } } private void saveSettings() throws MessagingException { + if (!mFolder.setLocalOnly(mLocalOnly.isChecked())) { + Log.e(K9.LOG_TAG, "Setting local-only status of folder failed. Ignoring all changes."); + // ASH make toast + return; + } 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(); @@ -169,20 +173,6 @@ public class FolderSettings extends K9PreferenceActivity { 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/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index 277179ce6..a34c095d2 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -38,6 +38,7 @@ import com.fsck.k9.R; import com.fsck.k9.SearchSpecification; import com.fsck.k9.K9.Intents; import com.fsck.k9.activity.FolderList; +import com.fsck.k9.activity.MessageInfoHolder; import com.fsck.k9.activity.MessageList; import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.power.TracingPowerManager; @@ -1976,7 +1977,7 @@ public class MessagingController implements Runnable { * first checking to be sure that the server message is not newer than the local message. Once * the local message is successfully processed its UID is updated to reflect the remote UID. * - * @param command arguments = (String folder, String uid) + * @param command arguments = (String folder, String uid, [String uid, ...]) * @param account * @throws MessagingException */ @@ -1987,7 +1988,8 @@ public class MessagingController implements Runnable { try { String folder = command.arguments[0]; - String uid = command.arguments[1]; + List uids = new ArrayList(Arrays.asList(command.arguments)); + uids.remove(0); if (account.getErrorFolderName().equals(folder)) { return; @@ -1995,18 +1997,11 @@ 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) { - return; - } - Store remoteStore = account.getRemoteStore(); remoteFolder = remoteStore.getFolder(folder); if (!remoteFolder.exists()) { @@ -2019,88 +2014,103 @@ public class MessagingController implements Runnable { return; } - Message remoteMessage = null; - if (!localMessage.getUid().startsWith(K9.LOCAL_UID_PREFIX)) { - remoteMessage = remoteFolder.getMessage(localMessage.getUid()); - } + for (String uid : uids) { + LocalMessage localMessage = (LocalMessage) localFolder.getMessage(uid); - if (remoteMessage == null) { - if (localMessage.isSet(Flag.X_REMOTE_COPY_STARTED)) { - Log.w(K9.LOG_TAG, "Local message with uid " + localMessage.getUid() + - " has flag " + Flag.X_REMOTE_COPY_STARTED + " already set, checking for remote message with " + - " same message id"); - String rUid = remoteFolder.getUidFromMessageId(localMessage); - if (rUid != null) { - Log.w(K9.LOG_TAG, "Local message has flag " + Flag.X_REMOTE_COPY_STARTED + " already set, and there is a remote message with " + - " uid " + rUid + ", assuming message was already copied and aborting this copy"); + if (localMessage == null) { + continue; + } + Message remoteMessage = null; + if (!localMessage.getUid().startsWith(K9.LOCAL_UID_PREFIX)) { + remoteMessage = remoteFolder.getMessage(localMessage.getUid()); + } + + if (remoteMessage == null) { + if (localMessage.isSet(Flag.X_REMOTE_COPY_STARTED)) { + Log.w(K9.LOG_TAG, "Local message with uid " + localMessage.getUid() + + " has flag " + Flag.X_REMOTE_COPY_STARTED + + " already set, checking for remote message with " + + " same message id"); + String rUid = remoteFolder.getUidFromMessageId(localMessage); + if (rUid != null) { + Log.w(K9.LOG_TAG, "Local message has flag " + Flag.X_REMOTE_COPY_STARTED + + " already set, and there is a remote message with " + " uid " + + rUid + + ", assuming message was already copied and aborting this copy"); + + String oldUid = localMessage.getUid(); + localMessage.setUid(rUid); + localFolder.changeUid(localMessage); + for (MessagingListener l : getListeners()) { + l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); + // ASH or should below be: l.listLocalMessagesUpdateMessage(account, folder, localMessage); + l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage); + } + continue; + } else { + Log.w(K9.LOG_TAG, "No remote message with message-id found, proceeding with append"); + } + } + + /* + * 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); + String oldUid = localMessage.getUid(); + localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true); + remoteFolder.appendMessages(new Message[] { localMessage }); + + localFolder.changeUid(localMessage); + for (MessagingListener l : getListeners()) { + l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); + // ASH or should below be: l.listLocalMessagesUpdateMessage(account, folder, localMessage); + l.synchronizeMailboxAddOrUpdateMessage(account, folder, localMessage); + } + } else { + /* + * If the remote message exists we need to determine which copy to keep. + */ + /* + * See if the remote message is newer than ours. + */ + FetchProfile fp = new FetchProfile(); + fp.add(FetchProfile.Item.ENVELOPE); + remoteFolder.fetch(new Message[] { remoteMessage }, fp, null); + Date localDate = localMessage.getInternalDate(); + Date remoteDate = remoteMessage.getInternalDate(); + if (remoteDate != null && remoteDate.compareTo(localDate) > 0) { + /* + * If the remote message is newer than ours we'll just + * delete ours and move on. A sync will get the server message + * if we need to be able to see it. + */ + localMessage.destroy(); + } else { + /* + * Otherwise we'll upload our message and then delete the remote message. + */ + fp.clear(); + fp = new FetchProfile(); + fp.add(FetchProfile.Item.BODY); + localFolder.fetch(new Message[] { localMessage }, fp, null); String oldUid = localMessage.getUid(); - localMessage.setUid(rUid); + + localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true); + + remoteFolder.appendMessages(new Message[] { localMessage }); localFolder.changeUid(localMessage); for (MessagingListener l : getListeners()) { l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); } - return; - } else { - Log.w(K9.LOG_TAG, "No remote message with message-id found, proceeding with append"); - } - } - - /* - * 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); - String oldUid = localMessage.getUid(); - localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true); - remoteFolder.appendMessages(new Message[] { localMessage }); - - localFolder.changeUid(localMessage); - for (MessagingListener l : getListeners()) { - l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); - } - } else { - /* - * If the remote message exists we need to determine which copy to keep. - */ - /* - * See if the remote message is newer than ours. - */ - FetchProfile fp = new FetchProfile(); - fp.add(FetchProfile.Item.ENVELOPE); - remoteFolder.fetch(new Message[] { remoteMessage }, fp, null); - Date localDate = localMessage.getInternalDate(); - Date remoteDate = remoteMessage.getInternalDate(); - if (remoteDate != null && remoteDate.compareTo(localDate) > 0) { - /* - * If the remote message is newer than ours we'll just - * delete ours and move on. A sync will get the server message - * if we need to be able to see it. - */ - localMessage.destroy(); - } else { - /* - * Otherwise we'll upload our message and then delete the remote message. - */ - fp.clear(); - fp = new FetchProfile(); - fp.add(FetchProfile.Item.BODY); - localFolder.fetch(new Message[] { localMessage }, fp, null); - String oldUid = localMessage.getUid(); - - localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true); - - remoteFolder.appendMessages(new Message[] { localMessage }); - localFolder.changeUid(localMessage); - for (MessagingListener l : getListeners()) { - l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); - } - if (remoteDate != null) { - remoteMessage.setFlag(Flag.DELETED, true); - if (Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) { - remoteFolder.expunge(); + if (remoteDate != null) { + remoteMessage.setFlag(Flag.DELETED, true); + if (Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) { + remoteFolder.expunge(); + } } } } @@ -2231,7 +2241,7 @@ public class MessagingController implements Runnable { Folder remoteFolder = null; LocalStore localStore = account.getLocalStore(); LocalFolder localFolder = localStore.getFolder(folder); - localFolder.open(OpenMode.READ_WRITE); + localFolder.open(OpenMode.READ_WRITE); // ASH is this needed? String[] queryFields = { "uid" }; String queryString = K9.LOCAL_UID_PREFIX; List folders = Arrays.asList(new LocalFolder[] { localFolder }); @@ -2296,22 +2306,58 @@ 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); + /* + * Changes all non-local UIDs to local UIDs in a folder. If a non-local message is not fully + * downloaded, an attempt will be made to do so before changing the UID. + * + * @param folder the folder to localize + * @param download whether to attempt download of partially downloaded messages or not. + * @return the status if all messages in folder have local UIDs. + */ + public boolean localizeUids(LocalFolder folder, boolean download) throws MessagingException { + String folderName = folder.getName(); + Message[] messages = folder.getMessages(null); + if (messages.length == 0) { + return true; + } + Log.i(K9.LOG_TAG, "Changing UIDs of messages in folder " + folderName + " to local UIDs."); + boolean completed = true; + for (Message message : messages) { + String oldUid = message.getUid(); + Log.d("ASH", "old UID = " + oldUid); + if (!oldUid.startsWith(K9.LOCAL_UID_PREFIX)) { - for (MessagingListener l : getListeners()) { - l.messageUidChanged(folder.getAccount(), folder.getName(), oldUid, newUid); + if (download && message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { + // fully download message first + Log.d("ASH", "downloading message..."); + if (loadMessageForViewRemoteSynchronous(folder.getAccount(), folderName, + message.getUid(), null, true)) { + Log.d("ASH", "downloaded message"); + message.setFlag(Flag.X_DOWNLOADED_FULL, true); + message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); + } else { + completed = false; + Log.e(K9.LOG_TAG, "Cannot download message " + message.getUid() + + " in folder " + folderName); + Toast.makeText(mApplication, "Cannot download message " + + message.getSubject() + " in folder " + folderName, + Toast.LENGTH_LONG).show(); + continue; } } + + + 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); + } } + } + return completed; } private void queueSetFlag(final Account account, final String folderName, final String newState, final String flag, final String[] uids) { @@ -2656,13 +2702,13 @@ public class MessagingController implements Runnable { put("loadMessageForViewRemote", listener, new Runnable() { @Override public void run() { - loadMessageForViewRemoteSynchronous(account, folder, uid, listener); + loadMessageForViewRemoteSynchronous(account, folder, uid, listener, false); } }); } public boolean loadMessageForViewRemoteSynchronous(final Account account, final String folder, - final String uid, final MessagingListener listener) { + final String uid, final MessagingListener listener, final boolean force) { Folder remoteFolder = null; LocalFolder localFolder = null; try { @@ -2678,7 +2724,7 @@ public class MessagingController implements Runnable { Toast.LENGTH_LONG).show(); message.setFlag(Flag.X_DOWNLOADED_FULL, true); message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); - } else if (localFolder.isLocalOnly()) { + } else if (localFolder.isLocalOnly() && !force) { 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(); @@ -3291,9 +3337,7 @@ public class MessagingController implements Runnable { 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 instead, add all messages, and later handle local ones separately? @@ -3302,14 +3346,31 @@ public class MessagingController implements Runnable { } else { localUids.add(uid); if (isCopy && !uid.startsWith(K9.LOCAL_UID_PREFIX)) { + // somehow a non local-only UID exists in a local-only folder needToLocalizeSourceFolder = true; } } } + // make sure the remote folder actually gets created even though it won't be used yet. + // useful for the first time moving to archive/spam. + // ASH is this still needed since setLocalOnly(false) will create it? + // ASH how does this work on pop? + /*if (!account.isAutoUploadOnMove() && localUids.size() > 0 && + !localDestFolder.exists()) { + if (!localDestFolder.isLocalOnly()) { + Folder remoteDestFolder = remoteStore.getFolder(destFolder); + if (!remoteDestFolder.exists()) { + if (!remoteDestFolder.create()) { + localDestFolder.setLocalOnly(true); + // ASH warn + } + } + } + }*/ + 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; @@ -3317,15 +3378,18 @@ public class MessagingController implements Runnable { for (Message message : messages) { if (checkForPartialDownload && message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { - // fully download message if it's moved/coied to a local-only folder + // fully download message if it's moved/copied to a local-only folder Log.d("ASH", "downloading message..."); if (loadMessageForViewRemoteSynchronous(account, srcFolder, - message.getUid(), listener)) { + message.getUid(), listener, false)) { 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 { + if (!isCopy) { + unsuppressMessage(account, srcFolder, message.getUid()); + } Log.e(K9.LOG_TAG, "Cannot download message " + message.getUid() + " in folder " + srcFolder + " -- skipping it for move/copy."); Toast.makeText(mApplication, "Cannot download message " + @@ -3426,9 +3490,7 @@ public class MessagingController implements Runnable { 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); - } + appendMessages(account, localMessages, destFolder); } else if (isCopy) { // local message copy to local folder localSrcFolder.copyMessages(localMessages, localDestFolder); @@ -3437,7 +3499,7 @@ public class MessagingController implements Runnable { processPendingCommands(account); if (needToLocalizeSourceFolder) { - localizeUids(localSrcFolder); + localizeUids(localSrcFolder, false); } } catch (UnavailableStorageException e) { Log.i(K9.LOG_TAG, "Failed to move/copy message because storage is not available - trying again later."); @@ -3523,8 +3585,28 @@ public class MessagingController implements Runnable { } else { localTrashFolder = localStore.getFolder(account.getTrashFolderName()); if (!localTrashFolder.exists()) { - if (account.getRemoteStore().isMoveCapable()) { - localTrashFolder.create(); + if (account.getRemoteStore().isMoveCapable() && + account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) { + localTrashFolder.create(false); + // make sure the remote folder actually gets created even though it won't be used yet. + // ASH is this still needed since setLocalOnly(false) will create it? + /*boolean hasRemoteMessages = false; + for (String uid : uids) { + if (!uid.startsWith(K9.LOCAL_UID_PREFIX)) { + hasRemoteMessages = true; + break; + } + } + if (!hasRemoteMessages) { + Folder remoteTrashFolder = account.getRemoteStore() + .getFolder(account.getTrashFolderName()); + if (!remoteTrashFolder.exists()) { + if (!remoteTrashFolder.create()) { + localTrashFolder.setLocalOnly(true); + // ASH warn + } + } + }*/ } else { localTrashFolder.create(true); } @@ -3533,24 +3615,22 @@ public class MessagingController implements Runnable { 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)) { + message.getUid(), listener, false)) { 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); + unsuppressMessage(account, folder, message.getUid()); 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 + @@ -3563,9 +3643,6 @@ public class MessagingController implements Runnable { } 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; @@ -3588,7 +3665,6 @@ public class MessagingController implements Runnable { if (folder.equals(account.getOutboxFolderName())) { 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 @@ -3603,7 +3679,6 @@ public class MessagingController implements Runnable { } } 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); @@ -4270,43 +4345,16 @@ public class MessagingController implements Runnable { * Save a draft message. * @param account Account we are saving for. * @param message Message to save. - * @param existingDraftId * @return Message representing the entry in the local store. */ public Message saveDraft(final Account account, final Message message, long existingDraftId) { - return saveMessage(account, message, account.getDraftsFolderName(), existingDraftId, true); - } - - /** - * Save a message. - * @param account Account we are saving for. - * @param message Message to save. - * @param folderName String to save to. - * @return Message representing the entry in the local store. - */ - public Message saveMessage(final Account account, final Message message, final String - folderName) { - return saveMessage(account, message, folderName, INVALID_MESSAGE_ID, false); - } - - /** - * Save a message. - * @param account Account we are saving for. - * @param message Message to save. - * @param folderName String to save to. - * @param existingDraftId - * @param isDraft - * @return Message representing the entry in the local store. - */ - public Message saveMessage(final Account account, final Message message, final String - folderName, final long existingDraftId, final boolean isDraft) { Message localMessage = null; try { LocalStore localStore = account.getLocalStore(); - LocalFolder localFolder = localStore.getFolder(folderName); + LocalFolder localFolder = localStore.getFolder(account.getDraftsFolderName()); localFolder.open(OpenMode.READ_WRITE); - if (isDraft && existingDraftId != INVALID_MESSAGE_ID) { + if (existingDraftId != INVALID_MESSAGE_ID) { String uid = localFolder.getMessageUidById(existingDraftId); message.setUid(uid); } @@ -4319,21 +4367,77 @@ public class MessagingController implements Runnable { localMessage = localFolder.getMessage(message.getUid()); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); - 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); - } + 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); + Log.e(K9.LOG_TAG, "Unable to save message as draft.", e); addErrorMessage(account, null, e); } return localMessage; } + /** + * Append messages to remote store. + * @param account Account we are saving for. + * @param holders MessageInfoHolders with messages to save. + */ + public void appendMessages(final Account account, List holders) { + List messages = new ArrayList(holders.size()); + for (MessageInfoHolder holder : holders) { + messages.add(holder.message); + } + appendMessages(account, messages.toArray(EMPTY_MESSAGE_ARRAY), holders.get(0).folder.name); + } + /** + * Append messages to remote store. + * @param account Account we are saving for. + * @param messages Messages to save. + * @param folderName Folder name to save to. + */ + public void appendMessages(final Account account, final Message[] messages, final String + folderName) { + try { + LocalFolder localFolder = account.getLocalStore().getFolder(folderName); +// ASH localFolder.open(OpenMode.READ_WRITE); + List commandArguments = new ArrayList(); + commandArguments.add(folderName); + + if (!localFolder.isLocalOnly() && account.getRemoteStore().isAppendCapable()) { + for (Message message : messages) { + if (message.getUid().startsWith(K9.LOCAL_UID_PREFIX)) { + commandArguments.add(message.getUid()); + } + } + if (commandArguments.size() < 2) { + Log.w(K9.LOG_TAG, "No messages to append."); + return; + } + } else { + Log.w(K9.LOG_TAG, "Unable to upload messages to " + folderName + " because " + + (localFolder.isLocalOnly() ? "folder" : "remote") + " is not " + + (localFolder.isLocalOnly() ? "syncable" : "appendable")); + return; + } + + PendingCommand command = new PendingCommand(); + command.command = PENDING_COMMAND_APPEND; + command.arguments = commandArguments.toArray(EMPTY_STRING_ARRAY); + queuePendingCommand(account, command); + processPendingCommands(account); + + } catch (MessagingException e) { + Log.e(K9.LOG_TAG, "Unable to upload messages to " + folderName + ".", e); + addErrorMessage(account, null, e); + } + } + public long getId(Message message) { long id; if (message instanceof LocalMessage) { diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 7485530f2..7ceba923e 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -37,6 +37,7 @@ import com.fsck.k9.Preferences; import com.fsck.k9.R; import com.fsck.k9.controller.MessageRemovalListener; import com.fsck.k9.controller.MessageRetrievalListener; +import com.fsck.k9.controller.MessagingController; import com.fsck.k9.helper.Utility; import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Body; @@ -371,13 +372,14 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); try { db.execSQL("ALTER TABLE folders ADD local_only INTEGER default 0"); + // ASH this might fuck things up if remoteStore is unavailable. queue it all somehow. List remoteFolders = mAccount.getRemoteStore().getPersonalNamespaces(false); HashSet remoteFolderNames = new HashSet(); for (Folder remoteFolder : remoteFolders) { remoteFolderNames.add(remoteFolder.getName()); } - // ASH verify that this works properly -- still untested! + // ASH verify that this works properly -- still untested! maybe need localFolder.save() somewhere? List localFolders = getPersonalNamespaces(true); for (LocalFolder localFolder : localFolders) { if (remoteFolderNames.contains(localFolder.getName()) == false) { @@ -386,9 +388,17 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); localFolder.setLocalOnly(true); Log.w(K9.LOG_TAG, "Setting folder " + localFolder.getName() + " to local-only folder."); + } else if (localFolder.getName().equals(mAccount.getTrashFolderName()) + && mAccount.getDeletePolicy() != + Account.DELETE_POLICY_ON_DELETE) { + // trash folder is local-only depending on delete policy + db.execSQL("UPDATE messages SET local_only = 1 WHERE name = " + + localFolder.getName()); + localFolder.setLocalOnly(true); + Log.w(K9.LOG_TAG, "Setting folder " + localFolder.getName() + + " to local-only folder due to delete policy."); } } - } catch (SQLiteException e) { if (! e.getMessage().startsWith("duplicate column name: local_only")) { throw e; @@ -435,6 +445,7 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); Folder.FolderClass pushClass = Folder.FolderClass.SECOND_CLASS; boolean inTopGroup = false; boolean integrate = false; + boolean isLocalOnly = false; if (mAccount.getInboxFolderName().equals(name)) { displayClass = Folder.FolderClass.FIRST_CLASS; syncClass = Folder.FolderClass.FIRST_CLASS; @@ -449,6 +460,7 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); 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); + isLocalOnly = prefs.getBoolean(uUid + "." + name + ".isLocalOnly", isLocalOnly); } catch (Exception e) { Log.e(K9.LOG_TAG, " Throwing away an error while trying to upgrade folder metadata", e); } @@ -463,8 +475,8 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); 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 }); + db.execSQL("UPDATE folders SET integrate = ?, top_group = ?, poll_class=?, push_class =?, display_class = ?, local_only = ? WHERE id = ?", + new Object[] { integrate, inTopGroup, syncClass, pushClass, displayClass, isLocalOnly, id }); } } @@ -1107,6 +1119,11 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); } }); Log.i(K9.LOG_TAG, "Renamed folder " + oldFolderName + " to " + newFolderName); +Log.d("ASH", "OldFolder.delete() pre"); + oldFolder.delete(); +Log.d("ASH", "newFolder.save() pre"); + newFolder.save(); +Log.d("ASH", "newFolder.save() post"); return true; } return false; @@ -1171,11 +1188,13 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); try { folder.setLocalOnly(localOnly); +Log.d("ASH", "folder.save() pre"); + folder.save(); +Log.d("ASH", "folder.save() post"); } catch (MessagingException me) { Log.e(K9.LOG_TAG, "Exception trying to set local-only status of folder " + name + " to " + localOnly); } - } return null; } @@ -1199,7 +1218,7 @@ Log.d("ASH", "updatedb " + mAccount.getDescription()); private boolean mInTopGroup = false; private String mPushState = null; private boolean mIntegrate = false; - private boolean mLocalOnly = false; + private Boolean mLocalOnly; // 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; @@ -1452,7 +1471,7 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ? return ; } open(OpenMode.READ_WRITE); - Message[] messages = getMessages(null, false); + Message[] messages = getMessages(null, false, false); for (int i = mVisibleLimit; i < messages.length; i++) { if (listener != null) { listener.messageRemoved(messages[i]); @@ -1555,13 +1574,57 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ? updateFolderColumn("integrate", mIntegrate ? 1 : 0); } - public void setLocalOnly(boolean localOnly) throws MessagingException { - mLocalOnly = localOnly; -Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly); - updateFolderColumn("local_only", localOnly == true ? "1" : "0"); + public boolean setLocalOnly(boolean localOnly) throws MessagingException { + // ASH was this here for a good reason? + /*if (isLocalOnly() != null && mLocalOnly == localOnly) { + Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly + " UNNECESSARY"); + return true; + }*/ + Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly); + + if (localOnly) { + if (!MessagingController.getInstance(K9.app).localizeUids(this, true)) { + // could not download all messages for some reason + //if (mAccount.getRemoteStore().getFolder(mName).exists()) { + Log.e(K9.LOG_TAG, "Unable to localize folder " + mName + " at this time"); + // ASH make toast? no, this should not be done in LocalFolder but in an activity. + return false; + //} + } + mLocalOnly = true; + updateFolderColumn("local_only", "1"); + } else { + // ASH can maybe delete logic in onCreateFolder() and elsewhere. + Store store = mAccount.getRemoteStore(); + if (store.isMoveCapable()) { + Folder folder = store.getFolder(mName); + if (!folder.exists()) { + Log.i(K9.LOG_TAG, "Creating remote folder " + mName); + if (!folder.create()) { + Log.e(K9.LOG_TAG, "Unable to create remote folder " + mName); + // ASH make toast? no, this should not be done in LocalFolder but in an activity. + return false; + } + } + } + mLocalOnly = false; + updateFolderColumn("local_only", "0"); + } + return true; } - public boolean isLocalOnly() { + public Boolean isLocalOnly() { + Log.v("ASH", "### " + mName + " : " + mLocalOnly); + if (mLocalOnly == null) { + try { + open(OpenMode.READ_ONLY); + } catch (MessagingException e) { + Log.e(K9.LOG_TAG, "Exception opening folder " + mName); + } finally { + close(); + } + } + Log.v("ASH", "#!# " + mName + " : " + mLocalOnly); return mLocalOnly; } @@ -1589,6 +1652,7 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly); editor.remove(id + ".pushMode"); editor.remove(id + ".inTopGroup"); editor.remove(id + ".integrate"); + editor.remove(id + ".isLocalOnly"); editor.commit(); } @@ -1623,7 +1687,7 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly); editor.putBoolean(id + ".inTopGroup", mInTopGroup); editor.putBoolean(id + ".integrate", mIntegrate); - + editor.putBoolean(id + ".isLocalOnly", mLocalOnly); } public void refresh(String name, PreferencesHolder prefHolder) { @@ -1987,18 +2051,25 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly); @Override public Message[] getMessages(final MessageRetrievalListener listener, final boolean includeDeleted) throws MessagingException { + return getMessages(listener, includeDeleted, true); + } + + public Message[] getMessages(final MessageRetrievalListener listener, final boolean + includeDeleted, final boolean includeLocal) throws MessagingException { try { return database.execute(false, new DbCallback() { @Override public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException { try { open(OpenMode.READ_WRITE); + String excludeLocal = "uid NOT LIKE '" + K9.LOCAL_UID_PREFIX + "%' AND "; return LocalStore.this.getMessages( listener, LocalFolder.this, "SELECT " + GET_MESSAGES_COLS + "FROM messages WHERE " + (includeDeleted ? "" : "deleted = 0 AND ") + + (includeLocal ? "" : excludeLocal) + " folder_id = ? ORDER BY date DESC" , new String[] { Long.toString(mFolderId) diff --git a/src/com/fsck/k9/preferences/AccountSettings.java b/src/com/fsck/k9/preferences/AccountSettings.java index 8beeb7fa3..f34f26f78 100644 --- a/src/com/fsck/k9/preferences/AccountSettings.java +++ b/src/com/fsck/k9/preferences/AccountSettings.java @@ -157,6 +157,9 @@ public class AccountSettings { s.put("saveAllHeaders", Settings.versions( new V(1, new BooleanSetting(true)) )); + s.put("autoUploadOnMove", Settings.versions( + new V(1, new BooleanSetting(true)) + )); s.put("searchableFolders", Settings.versions( new V(1, new EnumSetting(Account.Searchable.class, Account.Searchable.ALL)) )); diff --git a/src/com/fsck/k9/preferences/FolderSettings.java b/src/com/fsck/k9/preferences/FolderSettings.java index 6759a71ae..4088662ee 100644 --- a/src/com/fsck/k9/preferences/FolderSettings.java +++ b/src/com/fsck/k9/preferences/FolderSettings.java @@ -35,6 +35,9 @@ public class FolderSettings { s.put("integrate", Settings.versions( new V(1, new BooleanSetting(false)) )); + s.put("isLocalOnly", Settings.versions( + new V(1, new BooleanSetting(false)) + )); SETTINGS = Collections.unmodifiableMap(s); diff --git a/src/com/fsck/k9/preferences/SettingsImporter.java b/src/com/fsck/k9/preferences/SettingsImporter.java index a20059f47..3c15891bc 100644 --- a/src/com/fsck/k9/preferences/SettingsImporter.java +++ b/src/com/fsck/k9/preferences/SettingsImporter.java @@ -494,6 +494,7 @@ public class SettingsImporter { for (Map.Entry setting : writeSettings.entrySet()) { String key = prefix + setting.getKey(); String value = setting.getValue(); +Log.d("ASH", "import: " + key + ":" + value); putString(editor, key, value); } }