1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-17 06:55:03 -05:00

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.
This commit is contained in:
ashley willis 2012-02-16 17:09:42 -06:00
parent fd97eb2da4
commit c46afade5f
14 changed files with 458 additions and 228 deletions

View File

@ -14,6 +14,7 @@ import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Store;
import com.fsck.k9.mail.store.LocalStore; 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;
import com.fsck.k9.mail.store.StorageManager.StorageProvider; import com.fsck.k9.mail.store.StorageManager.StorageProvider;
import com.fsck.k9.view.ColorChip; 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 String EXPUNGE_ON_POLL = "EXPUNGE_ON_POLL";
public static final int DELETE_POLICY_NEVER = 0; 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_ON_DELETE = 2;
public static final int DELETE_POLICY_MARK_AS_READ = 3; public static final int DELETE_POLICY_MARK_AS_READ = 3;
@ -82,7 +83,7 @@ public class Account implements BaseAccount {
/** /**
* <pre> * <pre>
* 0 - Never (DELETE_POLICY_NEVER) * 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) * 2 - When I delete from inbox (DELETE_POLICY_ON_DELETE)
* 3 - Mark as read (DELETE_POLICY_MARK_AS_READ) * 3 - Mark as read (DELETE_POLICY_MARK_AS_READ)
* </pre> * </pre>
@ -902,7 +903,29 @@ public class Account implements BaseAccount {
} }
public synchronized void setDeletePolicy(int deletePolicy) { 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) { 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() { public synchronized String getArchiveFolderName() {

View File

@ -12,6 +12,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; 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.AccountDescriptionPair;
import com.fsck.k9.preferences.SettingsImporter.ImportContents; import com.fsck.k9.preferences.SettingsImporter.ImportContents;
import com.fsck.k9.preferences.SettingsImporter.ImportResults; import com.fsck.k9.preferences.SettingsImporter.ImportResults;
import com.fsck.k9.preferences.Storage;
public class Accounts extends K9ListActivity implements OnItemClickListener, OnClickListener { 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_REMOVE_ACCOUNT = 1;
private static final int DIALOG_CLEAR_ACCOUNT = 2; private static final int DIALOG_CLEAR_ACCOUNT = 2;
private static final int DIALOG_RECREATE_ACCOUNT = 3; private static final int DIALOG_RECREATE_ACCOUNT = 3;
private static final String TRUE = "true";
private ConcurrentHashMap<String, AccountStats> accountStats = new ConcurrentHashMap<String, AccountStats>(); private ConcurrentHashMap<String, AccountStats> accountStats = new ConcurrentHashMap<String, AccountStats>();
private ConcurrentHashMap<BaseAccount, String> pendingWork = new ConcurrentHashMap<BaseAccount, String>(); private ConcurrentHashMap<BaseAccount, String> pendingWork = new ConcurrentHashMap<BaseAccount, String>();
@ -864,8 +867,31 @@ public class Accounts extends K9ListActivity implements OnItemClickListener, OnC
// Start services if necessary // Start services if necessary
K9.setServicesEnabled(mContext); 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<String, String> 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 // Get list of folders from remote server
MessagingController.getInstance(mApplication).listFolders(mAccount, true, null); MessagingController.getInstance(mApplication).listFolders(mAccount, true, null);
} catch (Exception e) { } catch (Exception e) {
Log.e(K9.LOG_TAG, "Something went while setting account passwords", e); Log.e(K9.LOG_TAG, "Something went while setting account passwords", e);
} }

View File

@ -310,6 +310,9 @@ public class ChooseFolder extends K9ListActivity {
if (folderName.matches("")) { if (folderName.matches("")) {
Toast.makeText(getApplication(), "Folder name not given!", Toast.LENGTH_LONG).show(); Toast.makeText(getApplication(), "Folder name not given!", Toast.LENGTH_LONG).show();
return; return;
} else if (folderName.toUpperCase().matches(Account.INBOX)) {
Toast.makeText(getApplication(), "Refuse to create a folder named INBOX!", Toast.LENGTH_LONG).show();
return;
} }
try { try {
Store store = mAccount.getRemoteStore(); Store store = mAccount.getRemoteStore();

View File

@ -494,6 +494,9 @@ public class FolderList extends K9ListActivity {
if (folderName.matches("")) { if (folderName.matches("")) {
Toast.makeText(getApplication(), "Folder name not given!", Toast.LENGTH_LONG).show(); Toast.makeText(getApplication(), "Folder name not given!", Toast.LENGTH_LONG).show();
return; return;
} else if (folderName.toUpperCase().matches(Account.INBOX)) {
Toast.makeText(getApplication(), "Refuse to create a folder named INBOX!", Toast.LENGTH_LONG).show();
return;
} }
try { try {
Store store = mAccount.getRemoteStore(); Store store = mAccount.getRemoteStore();

View File

@ -729,22 +729,12 @@ public class MessageList
mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount); mCurrentFolder = mAdapter.getFolder(mFolderName, mAccount);
} }
// ASH this seems wrong, but it works for now.
if (mCurrentFolder != null) { if (mCurrentFolder != null) {
LocalFolder folder = (LocalFolder)mCurrentFolder.folder; LocalFolder folder = (LocalFolder)mCurrentFolder.folder;
if (folder != null) { if (folder != null) {
try { mLocalOnly = folder.isLocalOnly();
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); 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 // 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.setBackgroundDrawable(message.message.getFolder().getAccount().generateColorChip().drawable());
holder.chip.getBackground().setAlpha(message.read ? 127 : 255); holder.chip.getBackground().setAlpha(message.read ? 127 : 255);
view.getBackground().setAlpha(message.downloaded ? 0 : 127); view.getBackground().setAlpha(message.downloaded ? 0 : 127);
// ASH funky bug is here?
if (message.uid.startsWith(K9.LOCAL_UID_PREFIX)) { if (message.uid.startsWith(K9.LOCAL_UID_PREFIX)) {
view.setBackgroundColor(message.message.getFolder().getAccount().getChipColor()); view.setBackgroundColor(message.message.getFolder().getAccount().getChipColor());
view.getBackground().setAlpha(31); view.getBackground().setAlpha(31);
@ -2730,19 +2721,7 @@ public class MessageList
if (holders.isEmpty()) { if (holders.isEmpty()) {
return; return;
} }
boolean isAppendCapable = false; mController.appendMessages(mAccount, holders);
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());
}
}
} }
/** /**

View File

@ -753,17 +753,7 @@ public class MessageView extends K9Activity implements OnClickListener {
} }
private void onUpload() { private void onUpload() {
boolean isAppendCapable = false; mController.appendMessages(mAccount, new Message[] { mMessage }, mMessageReference.folderName);
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 @Override

View File

@ -726,7 +726,9 @@ public class AccountSettings extends K9PreferenceActivity {
mAccount.setGoToUnreadMessageSearch(mNotificationOpensUnread.isChecked()); mAccount.setGoToUnreadMessageSearch(mNotificationOpensUnread.isChecked());
mAccount.setNotificationShowsUnreadCount(mNotificationUnreadCount.isChecked()); mAccount.setNotificationShowsUnreadCount(mNotificationUnreadCount.isChecked());
mAccount.setFolderTargetMode(Account.FolderMode.valueOf(mTargetMode.getValue())); mAccount.setFolderTargetMode(Account.FolderMode.valueOf(mTargetMode.getValue()));
Log.d("ASH", "Setting delete policy to " + mDeletePolicy.getValue());
mAccount.setDeletePolicy(Integer.parseInt(mDeletePolicy.getValue())); mAccount.setDeletePolicy(Integer.parseInt(mDeletePolicy.getValue()));
Log.d("ASH", "Have set delete policy to " + mAccount.getDeletePolicy());
if (mIsExpungeCapable) { if (mIsExpungeCapable) {
mAccount.setExpungePolicy(mExpungePolicy.getValue()); mAccount.setExpungePolicy(mExpungePolicy.getValue());
} }

View File

@ -232,6 +232,11 @@ public class AccountSetupBasics extends K9Activity
mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam)); mAccount.setSpamFolderName(getString(R.string.special_mailbox_name_spam));
} }
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); 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); AccountSetupCheckSettings.actionCheckSettings(this, mAccount, true, true);
} catch (UnsupportedEncodingException enc) { } catch (UnsupportedEncodingException enc) {
// This really shouldn't happen since the encoding is hardcoded to UTF-8 // 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.setDraftsFolderName(getString(R.string.special_mailbox_name_drafts));
mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash)); mAccount.setTrashFolderName(getString(R.string.special_mailbox_name_trash));
mAccount.setSentFolderName(getString(R.string.special_mailbox_name_sent)); 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()); AccountSetupAccountType.actionSelectAccountType(this, mAccount, mDefaultView.isChecked());
finish(); finish();

View File

@ -141,20 +141,24 @@ public class FolderSettings extends K9PreferenceActivity {
mLocalOnly = (CheckBoxPreference)findPreference(PREFERENCE_LOCAL_ONLY); mLocalOnly = (CheckBoxPreference)findPreference(PREFERENCE_LOCAL_ONLY);
mLocalOnly.setChecked(mFolder.isLocalOnly()); mLocalOnly.setChecked(mFolder.isLocalOnly());
if (store instanceof Pop3Store || mAccount.getInboxFolderName().equals(folderName) || if (store instanceof Pop3Store || mAccount.getInboxFolderName().equals(folderName) ||
mAccount.getOutboxFolderName().equals(folderName)) { mAccount.getOutboxFolderName().equals(folderName) ||
mAccount.getTrashFolderName().equals(folderName)) {
mLocalOnly.setEnabled(false); mLocalOnly.setEnabled(false);
} }
if (!K9.isShowAdvancedOptions()) {// ASH disabled for testing: || store instanceof Pop3Store) { if (!K9.isShowAdvancedOptions() || store instanceof Pop3Store) {
category.removePreference(mLocalOnly); category.removePreference(mLocalOnly);
} }
} }
private void saveSettings() throws MessagingException { 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.setInTopGroup(mInTopGroup.isChecked());
mFolder.setIntegrate(mIntegrate.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 // We call getPushClass() because display class changes can affect push class when push class is set to inherit
FolderClass oldPushClass = mFolder.getPushClass(); FolderClass oldPushClass = mFolder.getPushClass();
FolderClass oldDisplayClass = mFolder.getDisplayClass(); FolderClass oldDisplayClass = mFolder.getDisplayClass();
@ -169,20 +173,6 @@ public class FolderSettings extends K9PreferenceActivity {
mFolder.save(); 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 newPushClass = mFolder.getPushClass();
FolderClass newDisplayClass = mFolder.getDisplayClass(); FolderClass newDisplayClass = mFolder.getDisplayClass();

View File

@ -38,6 +38,7 @@ import com.fsck.k9.R;
import com.fsck.k9.SearchSpecification; import com.fsck.k9.SearchSpecification;
import com.fsck.k9.K9.Intents; import com.fsck.k9.K9.Intents;
import com.fsck.k9.activity.FolderList; import com.fsck.k9.activity.FolderList;
import com.fsck.k9.activity.MessageInfoHolder;
import com.fsck.k9.activity.MessageList; import com.fsck.k9.activity.MessageList;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.helper.power.TracingPowerManager; 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 * 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. * 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 * @param account
* @throws MessagingException * @throws MessagingException
*/ */
@ -1987,7 +1988,8 @@ public class MessagingController implements Runnable {
try { try {
String folder = command.arguments[0]; String folder = command.arguments[0];
String uid = command.arguments[1]; List<String> uids = new ArrayList<String>(Arrays.asList(command.arguments));
uids.remove(0);
if (account.getErrorFolderName().equals(folder)) { if (account.getErrorFolderName().equals(folder)) {
return; return;
@ -1995,18 +1997,11 @@ public class MessagingController implements Runnable {
LocalStore localStore = account.getLocalStore(); LocalStore localStore = account.getLocalStore();
localFolder = localStore.getFolder(folder); localFolder = localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE);
if (localFolder.isLocalOnly()) { if (localFolder.isLocalOnly()) {
return; return;
} }
LocalMessage localMessage = (LocalMessage) localFolder.getMessage(uid);
if (localMessage == null) {
return;
}
Store remoteStore = account.getRemoteStore(); Store remoteStore = account.getRemoteStore();
remoteFolder = remoteStore.getFolder(folder); remoteFolder = remoteStore.getFolder(folder);
if (!remoteFolder.exists()) { if (!remoteFolder.exists()) {
@ -2019,88 +2014,103 @@ public class MessagingController implements Runnable {
return; return;
} }
Message remoteMessage = null; for (String uid : uids) {
if (!localMessage.getUid().startsWith(K9.LOCAL_UID_PREFIX)) { LocalMessage localMessage = (LocalMessage) localFolder.getMessage(uid);
remoteMessage = remoteFolder.getMessage(localMessage.getUid());
}
if (remoteMessage == null) { if (localMessage == null) {
if (localMessage.isSet(Flag.X_REMOTE_COPY_STARTED)) { continue;
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");
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(); String oldUid = localMessage.getUid();
localMessage.setUid(rUid);
localMessage.setFlag(Flag.X_REMOTE_COPY_STARTED, true);
remoteFolder.appendMessages(new Message[] { localMessage });
localFolder.changeUid(localMessage); localFolder.changeUid(localMessage);
for (MessagingListener l : getListeners()) { for (MessagingListener l : getListeners()) {
l.messageUidChanged(account, folder, oldUid, localMessage.getUid()); l.messageUidChanged(account, folder, oldUid, localMessage.getUid());
} }
return; if (remoteDate != null) {
} else { remoteMessage.setFlag(Flag.DELETED, true);
Log.w(K9.LOG_TAG, "No remote message with message-id found, proceeding with append"); if (Account.EXPUNGE_IMMEDIATELY.equals(account.getExpungePolicy())) {
} remoteFolder.expunge();
} }
/*
* 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();
} }
} }
} }
@ -2231,7 +2241,7 @@ public class MessagingController implements Runnable {
Folder remoteFolder = null; Folder remoteFolder = null;
LocalStore localStore = account.getLocalStore(); LocalStore localStore = account.getLocalStore();
LocalFolder localFolder = localStore.getFolder(folder); LocalFolder localFolder = localStore.getFolder(folder);
localFolder.open(OpenMode.READ_WRITE); localFolder.open(OpenMode.READ_WRITE); // ASH is this needed?
String[] queryFields = { "uid" }; String[] queryFields = { "uid" };
String queryString = K9.LOCAL_UID_PREFIX; String queryString = K9.LOCAL_UID_PREFIX;
List<LocalFolder> folders = Arrays.asList(new LocalFolder[] { localFolder }); List<LocalFolder> 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); * Changes all non-local UIDs to local UIDs in a folder. If a non-local message is not fully
for (Message message : messages) { * downloaded, an attempt will be made to do so before changing the UID.
String oldUid = message.getUid(); *
Log.d("ASH", "old UID = " + oldUid); * @param folder the folder to localize
if (!oldUid.startsWith(K9.LOCAL_UID_PREFIX)) { * @param download whether to attempt download of partially downloaded messages or not.
String newUid = K9.LOCAL_UID_PREFIX + java.util.UUID.randomUUID().toString(); * @return the status if all messages in folder have local UIDs.
Log.d("ASH", "new UID = " + newUid); */
message.setUid(newUid); public boolean localizeUids(LocalFolder folder, boolean download) throws MessagingException {
folder.changeUid((LocalMessage)message); 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()) { if (download && message.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
l.messageUidChanged(folder.getAccount(), folder.getName(), oldUid, newUid); // 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) { 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() { put("loadMessageForViewRemote", listener, new Runnable() {
@Override @Override
public void run() { public void run() {
loadMessageForViewRemoteSynchronous(account, folder, uid, listener); loadMessageForViewRemoteSynchronous(account, folder, uid, listener, false);
} }
}); });
} }
public boolean loadMessageForViewRemoteSynchronous(final Account account, final String folder, 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; Folder remoteFolder = null;
LocalFolder localFolder = null; LocalFolder localFolder = null;
try { try {
@ -2678,7 +2724,7 @@ public class MessagingController implements Runnable {
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
message.setFlag(Flag.X_DOWNLOADED_FULL, true); message.setFlag(Flag.X_DOWNLOADED_FULL, true);
message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); 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."); 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.makeText(mApplication, "Message in local-only folder so cannot download fully",
Toast.LENGTH_LONG).show(); Toast.LENGTH_LONG).show();
@ -3291,9 +3337,7 @@ public class MessagingController implements Runnable {
List<String> uids = new LinkedList<String>(); List<String> uids = new LinkedList<String>();
List<String> localUids = new LinkedList<String>(); List<String> localUids = new LinkedList<String>();
localSrcFolder.open(OpenMode.READ_WRITE);
boolean needToLocalizeSourceFolder = false; boolean needToLocalizeSourceFolder = false;
for (Message message : inMessages) { for (Message message : inMessages) {
String uid = message.getUid(); String uid = message.getUid();
// ASH instead, add all messages, and later handle local ones separately? // ASH instead, add all messages, and later handle local ones separately?
@ -3302,14 +3346,31 @@ public class MessagingController implements Runnable {
} else { } else {
localUids.add(uid); localUids.add(uid);
if (isCopy && !uid.startsWith(K9.LOCAL_UID_PREFIX)) { if (isCopy && !uid.startsWith(K9.LOCAL_UID_PREFIX)) {
// somehow a non local-only UID exists in a local-only folder
needToLocalizeSourceFolder = true; 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); Message[] messages = localSrcFolder.getMessages(uids.toArray(EMPTY_STRING_ARRAY), null);
if (messages.length > 0) { if (messages.length > 0) {
localDestFolder.open(OpenMode.READ_WRITE);
boolean checkForPartialDownload = (!localSrcFolder.isLocalOnly() && boolean checkForPartialDownload = (!localSrcFolder.isLocalOnly() &&
localDestFolder.isLocalOnly()) ? true : false; localDestFolder.isLocalOnly()) ? true : false;
@ -3317,15 +3378,18 @@ public class MessagingController implements Runnable {
for (Message message : messages) { for (Message message : messages) {
if (checkForPartialDownload && message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { 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..."); Log.d("ASH", "downloading message...");
if (loadMessageForViewRemoteSynchronous(account, srcFolder, if (loadMessageForViewRemoteSynchronous(account, srcFolder,
message.getUid(), listener)) { message.getUid(), listener, false)) {
Log.d("ASH", "downloaded message"); Log.d("ASH", "downloaded message");
message.setFlag(Flag.X_DOWNLOADED_FULL, true); message.setFlag(Flag.X_DOWNLOADED_FULL, true);
message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false);
origUidMap.put(message.getUid(), message); origUidMap.put(message.getUid(), message);
} else { } else {
if (!isCopy) {
unsuppressMessage(account, srcFolder, message.getUid());
}
Log.e(K9.LOG_TAG, "Cannot download message " + message.getUid() + Log.e(K9.LOG_TAG, "Cannot download message " + message.getUid() +
" in folder " + srcFolder + " -- skipping it for move/copy."); " in folder " + srcFolder + " -- skipping it for move/copy.");
Toast.makeText(mApplication, "Cannot download message " + Toast.makeText(mApplication, "Cannot download message " +
@ -3426,9 +3490,7 @@ public class MessagingController implements Runnable {
if (account.isAutoUploadOnMove() && !localDestFolder.isLocalOnly() && if (account.isAutoUploadOnMove() && !localDestFolder.isLocalOnly() &&
(isCopy ? remoteStore.isCopyCapable() : remoteStore.isMoveCapable())) { (isCopy ? remoteStore.isCopyCapable() : remoteStore.isMoveCapable())) {
// local message copy/move to remote folder // local message copy/move to remote folder
for (Message message : localMessages) { appendMessages(account, localMessages, destFolder);
saveMessage(account, message, destFolder);
}
} else if (isCopy) { } else if (isCopy) {
// local message copy to local folder // local message copy to local folder
localSrcFolder.copyMessages(localMessages, localDestFolder); localSrcFolder.copyMessages(localMessages, localDestFolder);
@ -3437,7 +3499,7 @@ public class MessagingController implements Runnable {
processPendingCommands(account); processPendingCommands(account);
if (needToLocalizeSourceFolder) { if (needToLocalizeSourceFolder) {
localizeUids(localSrcFolder); localizeUids(localSrcFolder, false);
} }
} catch (UnavailableStorageException e) { } catch (UnavailableStorageException e) {
Log.i(K9.LOG_TAG, "Failed to move/copy message because storage is not available - trying again later."); 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 { } else {
localTrashFolder = localStore.getFolder(account.getTrashFolderName()); localTrashFolder = localStore.getFolder(account.getTrashFolderName());
if (!localTrashFolder.exists()) { if (!localTrashFolder.exists()) {
if (account.getRemoteStore().isMoveCapable()) { if (account.getRemoteStore().isMoveCapable() &&
localTrashFolder.create(); 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 { } else {
localTrashFolder.create(true); localTrashFolder.create(true);
} }
@ -3533,24 +3615,22 @@ public class MessagingController implements Runnable {
if (K9.DEBUG) if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Deleting messages in normal folder, moving"); 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, // download messages first if trash folder is local-only,
// otherwise it's not undoable. // otherwise it's not undoable.
if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE && if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE &&
localTrashFolder.isLocalOnly() && !localFolder.isLocalOnly()) { localTrashFolder.isLocalOnly() && !localFolder.isLocalOnly()) {
Map<String, Message> origUidMap = new HashMap<String, Message>(); Map<String, Message> origUidMap = new HashMap<String, Message>();
Map<String, Message> skipUidMap = new HashMap<String, Message>();
for (Message message : messages) { for (Message message : messages) {
if (message.isSet(Flag.X_DOWNLOADED_PARTIAL)) { if (message.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
Log.d("ASH", "downloading message..."); Log.d("ASH", "downloading message...");
if (loadMessageForViewRemoteSynchronous(account, folder, if (loadMessageForViewRemoteSynchronous(account, folder,
message.getUid(), listener)) { message.getUid(), listener, false)) {
Log.d("ASH", "downloaded message"); Log.d("ASH", "downloaded message");
message.setFlag(Flag.X_DOWNLOADED_FULL, true); message.setFlag(Flag.X_DOWNLOADED_FULL, true);
message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false); message.setFlag(Flag.X_DOWNLOADED_PARTIAL, false);
origUidMap.put(message.getUid(), message); origUidMap.put(message.getUid(), message);
} else { } 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."); Log.e(K9.LOG_TAG, "Cannot download message -- skipping it for move to trash.");
Toast.makeText(mApplication, "Cannot download message " + Toast.makeText(mApplication, "Cannot download message " +
message.getSubject() + " in folder " + folder + message.getSubject() + " in folder " + folder +
@ -3563,9 +3643,6 @@ public class MessagingController implements Runnable {
} }
messages = origUidMap.values().toArray(EMPTY_MESSAGE_ARRAY); messages = origUidMap.values().toArray(EMPTY_MESSAGE_ARRAY);
uids = origUidMap.keySet().toArray(EMPTY_STRING_ARRAY); uids = origUidMap.keySet().toArray(EMPTY_STRING_ARRAY);
for (String uid : skipUidMap.keySet()) {
unsuppressMessage(account, folder, uid);
}
if (messages.length == 0) { if (messages.length == 0) {
Log.w(K9.LOG_TAG, "Not deleting any messages."); Log.w(K9.LOG_TAG, "Not deleting any messages.");
return; return;
@ -3588,7 +3665,6 @@ public class MessagingController implements Runnable {
if (folder.equals(account.getOutboxFolderName())) { if (folder.equals(account.getOutboxFolderName())) {
localTrashFolder = (LocalFolder)localStore.getFolder(account.getTrashFolderName()); localTrashFolder = (LocalFolder)localStore.getFolder(account.getTrashFolderName());
localTrashFolder.open(OpenMode.READ_WRITE);
if (!localTrashFolder.isLocalOnly()) { if (!localTrashFolder.isLocalOnly()) {
for (Message message : messages) { for (Message message : messages) {
// If the message was in the Outbox, then it has been copied to local Trash, and has // 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()) { } else if (!localFolder.isLocalOnly()) {
localTrashFolder = (LocalFolder)localStore.getFolder(account.getTrashFolderName()); localTrashFolder = (LocalFolder)localStore.getFolder(account.getTrashFolderName());
localTrashFolder.open(OpenMode.READ_WRITE);
if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) { if (account.getDeletePolicy() == Account.DELETE_POLICY_ON_DELETE) {
if (folder.equals(account.getTrashFolderName()) || localTrashFolder.isLocalOnly()) { if (folder.equals(account.getTrashFolderName()) || localTrashFolder.isLocalOnly()) {
queueSetFlag(account, folder, Boolean.toString(true), Flag.DELETED.toString(), uids); queueSetFlag(account, folder, Boolean.toString(true), Flag.DELETED.toString(), uids);
@ -4270,43 +4345,16 @@ public class MessagingController implements Runnable {
* Save a draft message. * Save a draft message.
* @param account Account we are saving for. * @param account Account we are saving for.
* @param message Message to save. * @param message Message to save.
* @param existingDraftId
* @return Message representing the entry in the local store. * @return Message representing the entry in the local store.
*/ */
public Message saveDraft(final Account account, final Message message, long existingDraftId) { 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; Message localMessage = null;
try { try {
LocalStore localStore = account.getLocalStore(); LocalStore localStore = account.getLocalStore();
LocalFolder localFolder = localStore.getFolder(folderName); LocalFolder localFolder = localStore.getFolder(account.getDraftsFolderName());
localFolder.open(OpenMode.READ_WRITE); localFolder.open(OpenMode.READ_WRITE);
if (isDraft && existingDraftId != INVALID_MESSAGE_ID) { if (existingDraftId != INVALID_MESSAGE_ID) {
String uid = localFolder.getMessageUidById(existingDraftId); String uid = localFolder.getMessageUidById(existingDraftId);
message.setUid(uid); message.setUid(uid);
} }
@ -4319,21 +4367,77 @@ public class MessagingController implements Runnable {
localMessage = localFolder.getMessage(message.getUid()); localMessage = localFolder.getMessage(message.getUid());
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true); localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
if (!localFolder.isLocalOnly()) { PendingCommand command = new PendingCommand();
PendingCommand command = new PendingCommand(); command.command = PENDING_COMMAND_APPEND;
command.command = PENDING_COMMAND_APPEND; command.arguments = new String[] {
command.arguments = new String[] { localFolder.getName(), localMessage.getUid() }; localFolder.getName(),
queuePendingCommand(account, command); localMessage.getUid()
processPendingCommands(account); };
} queuePendingCommand(account, command);
processPendingCommands(account);
} catch (MessagingException e) { } 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); addErrorMessage(account, null, e);
} }
return localMessage; 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<MessageInfoHolder> holders) {
List<Message> messages = new ArrayList<Message>(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<String> commandArguments = new ArrayList<String>();
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) { public long getId(Message message) {
long id; long id;
if (message instanceof LocalMessage) { if (message instanceof LocalMessage) {

View File

@ -37,6 +37,7 @@ import com.fsck.k9.Preferences;
import com.fsck.k9.R; import com.fsck.k9.R;
import com.fsck.k9.controller.MessageRemovalListener; import com.fsck.k9.controller.MessageRemovalListener;
import com.fsck.k9.controller.MessageRetrievalListener; import com.fsck.k9.controller.MessageRetrievalListener;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body; import com.fsck.k9.mail.Body;
@ -371,13 +372,14 @@ Log.d("ASH", "updatedb " + mAccount.getDescription());
try { try {
db.execSQL("ALTER TABLE folders ADD local_only INTEGER default 0"); 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 <? extends Folder > remoteFolders = List <? extends Folder > remoteFolders =
mAccount.getRemoteStore().getPersonalNamespaces(false); mAccount.getRemoteStore().getPersonalNamespaces(false);
HashSet<String> remoteFolderNames = new HashSet<String>(); HashSet<String> remoteFolderNames = new HashSet<String>();
for (Folder remoteFolder : remoteFolders) { for (Folder remoteFolder : remoteFolders) {
remoteFolderNames.add(remoteFolder.getName()); 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 <? extends LocalFolder > localFolders = getPersonalNamespaces(true); List <? extends LocalFolder > localFolders = getPersonalNamespaces(true);
for (LocalFolder localFolder : localFolders) { for (LocalFolder localFolder : localFolders) {
if (remoteFolderNames.contains(localFolder.getName()) == false) { if (remoteFolderNames.contains(localFolder.getName()) == false) {
@ -386,9 +388,17 @@ Log.d("ASH", "updatedb " + mAccount.getDescription());
localFolder.setLocalOnly(true); localFolder.setLocalOnly(true);
Log.w(K9.LOG_TAG, "Setting folder " + localFolder.getName() + Log.w(K9.LOG_TAG, "Setting folder " + localFolder.getName() +
" to local-only folder."); " 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) { } catch (SQLiteException e) {
if (! e.getMessage().startsWith("duplicate column name: local_only")) { if (! e.getMessage().startsWith("duplicate column name: local_only")) {
throw e; throw e;
@ -435,6 +445,7 @@ Log.d("ASH", "updatedb " + mAccount.getDescription());
Folder.FolderClass pushClass = Folder.FolderClass.SECOND_CLASS; Folder.FolderClass pushClass = Folder.FolderClass.SECOND_CLASS;
boolean inTopGroup = false; boolean inTopGroup = false;
boolean integrate = false; boolean integrate = false;
boolean isLocalOnly = false;
if (mAccount.getInboxFolderName().equals(name)) { if (mAccount.getInboxFolderName().equals(name)) {
displayClass = Folder.FolderClass.FIRST_CLASS; displayClass = Folder.FolderClass.FIRST_CLASS;
syncClass = 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())); pushClass = Folder.FolderClass.valueOf(prefs.getString(uUid + "." + name + ".pushMode", pushClass.name()));
inTopGroup = prefs.getBoolean(uUid + "." + name + ".inTopGroup", inTopGroup); inTopGroup = prefs.getBoolean(uUid + "." + name + ".inTopGroup", inTopGroup);
integrate = prefs.getBoolean(uUid + "." + name + ".integrate", integrate); integrate = prefs.getBoolean(uUid + "." + name + ".integrate", integrate);
isLocalOnly = prefs.getBoolean(uUid + "." + name + ".isLocalOnly", isLocalOnly);
} catch (Exception e) { } catch (Exception e) {
Log.e(K9.LOG_TAG, " Throwing away an error while trying to upgrade folder metadata", 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; pushClass = Folder.FolderClass.INHERITED;
} }
db.execSQL("UPDATE folders SET integrate = ?, top_group = ?, poll_class=?, push_class =?, display_class = ? WHERE 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, 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.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 true;
} }
return false; return false;
@ -1171,11 +1188,13 @@ Log.d("ASH", "updatedb " + mAccount.getDescription());
try { try {
folder.setLocalOnly(localOnly); folder.setLocalOnly(localOnly);
Log.d("ASH", "folder.save() pre");
folder.save();
Log.d("ASH", "folder.save() post");
} catch (MessagingException me) { } catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Exception trying to set local-only status of folder " + Log.e(K9.LOG_TAG, "Exception trying to set local-only status of folder " +
name + " to " + localOnly); name + " to " + localOnly);
} }
} }
return null; return null;
} }
@ -1199,7 +1218,7 @@ Log.d("ASH", "updatedb " + mAccount.getDescription());
private boolean mInTopGroup = false; private boolean mInTopGroup = false;
private String mPushState = null; private String mPushState = null;
private boolean mIntegrate = false; 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 // 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. // know whether or not an unread message added to the local folder is actually "new" or not.
private Integer mLastUid = null; private Integer mLastUid = null;
@ -1452,7 +1471,7 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
return ; return ;
} }
open(OpenMode.READ_WRITE); open(OpenMode.READ_WRITE);
Message[] messages = getMessages(null, false); Message[] messages = getMessages(null, false, false);
for (int i = mVisibleLimit; i < messages.length; i++) { for (int i = mVisibleLimit; i < messages.length; i++) {
if (listener != null) { if (listener != null) {
listener.messageRemoved(messages[i]); listener.messageRemoved(messages[i]);
@ -1555,13 +1574,57 @@ Log.v("ASH", mAccount.getDescription() + ":" + name + " is " + (localOnly == 1 ?
updateFolderColumn("integrate", mIntegrate ? 1 : 0); updateFolderColumn("integrate", mIntegrate ? 1 : 0);
} }
public void setLocalOnly(boolean localOnly) throws MessagingException { public boolean setLocalOnly(boolean localOnly) throws MessagingException {
mLocalOnly = localOnly; // ASH was this here for a good reason?
Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly); /*if (isLocalOnly() != null && mLocalOnly == localOnly) {
updateFolderColumn("local_only", localOnly == true ? "1" : "0"); 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; return mLocalOnly;
} }
@ -1589,6 +1652,7 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly);
editor.remove(id + ".pushMode"); editor.remove(id + ".pushMode");
editor.remove(id + ".inTopGroup"); editor.remove(id + ".inTopGroup");
editor.remove(id + ".integrate"); editor.remove(id + ".integrate");
editor.remove(id + ".isLocalOnly");
editor.commit(); editor.commit();
} }
@ -1623,7 +1687,7 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly);
editor.putBoolean(id + ".inTopGroup", mInTopGroup); editor.putBoolean(id + ".inTopGroup", mInTopGroup);
editor.putBoolean(id + ".integrate", mIntegrate); editor.putBoolean(id + ".integrate", mIntegrate);
editor.putBoolean(id + ".isLocalOnly", mLocalOnly);
} }
public void refresh(String name, PreferencesHolder prefHolder) { public void refresh(String name, PreferencesHolder prefHolder) {
@ -1987,18 +2051,25 @@ Log.d("ASH", "setting folder " + mName + " to localOnly = " + localOnly);
@Override @Override
public Message[] getMessages(final MessageRetrievalListener listener, final boolean includeDeleted) throws MessagingException { 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 { try {
return database.execute(false, new DbCallback<Message[]>() { return database.execute(false, new DbCallback<Message[]>() {
@Override @Override
public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException { public Message[] doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
try { try {
open(OpenMode.READ_WRITE); open(OpenMode.READ_WRITE);
String excludeLocal = "uid NOT LIKE '" + K9.LOCAL_UID_PREFIX + "%' AND ";
return LocalStore.this.getMessages( return LocalStore.this.getMessages(
listener, listener,
LocalFolder.this, LocalFolder.this,
"SELECT " + GET_MESSAGES_COLS "SELECT " + GET_MESSAGES_COLS
+ "FROM messages WHERE " + "FROM messages WHERE "
+ (includeDeleted ? "" : "deleted = 0 AND ") + (includeDeleted ? "" : "deleted = 0 AND ")
+ (includeLocal ? "" : excludeLocal)
+ " folder_id = ? ORDER BY date DESC" + " folder_id = ? ORDER BY date DESC"
, new String[] { , new String[] {
Long.toString(mFolderId) Long.toString(mFolderId)

View File

@ -157,6 +157,9 @@ public class AccountSettings {
s.put("saveAllHeaders", Settings.versions( s.put("saveAllHeaders", Settings.versions(
new V(1, new BooleanSetting(true)) new V(1, new BooleanSetting(true))
)); ));
s.put("autoUploadOnMove", Settings.versions(
new V(1, new BooleanSetting(true))
));
s.put("searchableFolders", Settings.versions( s.put("searchableFolders", Settings.versions(
new V(1, new EnumSetting(Account.Searchable.class, Account.Searchable.ALL)) new V(1, new EnumSetting(Account.Searchable.class, Account.Searchable.ALL))
)); ));

View File

@ -35,6 +35,9 @@ public class FolderSettings {
s.put("integrate", Settings.versions( s.put("integrate", Settings.versions(
new V(1, new BooleanSetting(false)) new V(1, new BooleanSetting(false))
)); ));
s.put("isLocalOnly", Settings.versions(
new V(1, new BooleanSetting(false))
));
SETTINGS = Collections.unmodifiableMap(s); SETTINGS = Collections.unmodifiableMap(s);

View File

@ -494,6 +494,7 @@ public class SettingsImporter {
for (Map.Entry<String, String> setting : writeSettings.entrySet()) { for (Map.Entry<String, String> setting : writeSettings.entrySet()) {
String key = prefix + setting.getKey(); String key = prefix + setting.getKey();
String value = setting.getValue(); String value = setting.getValue();
Log.d("ASH", "import: " + key + ":" + value);
putString(editor, key, value); putString(editor, key, value);
} }
} }