From 0d368c916ae2bb799483a9530cc01d2ee21a249f Mon Sep 17 00:00:00 2001 From: Kris Wong Date: Sun, 22 Jan 2012 16:01:31 -0500 Subject: [PATCH] For EAS accounts, display folder names in the list with parent folder information (i.e., parent/child). Also add support for updating folder names when refreshing the folder list. --- .../k9/controller/MessagingController.java | 9 +- src/com/fsck/k9/mail/Folder.java | 4 - src/com/fsck/k9/mail/store/EasStore.java | 54 ++++- src/com/fsck/k9/mail/store/LocalStore.java | 5 + .../exchange/adapter/FolderSyncParser.java | 216 ++---------------- 5 files changed, 78 insertions(+), 210 deletions(-) diff --git a/src/com/fsck/k9/controller/MessagingController.java b/src/com/fsck/k9/controller/MessagingController.java index e7bbcc638..e20ef3f59 100644 --- a/src/com/fsck/k9/controller/MessagingController.java +++ b/src/com/fsck/k9/controller/MessagingController.java @@ -503,19 +503,22 @@ public class MessagingController implements Runnable { private void syncLocalFoldersWithRemoteFolders(List localFolders, List remoteFolders, final Account account) throws MessagingException { HashSet remoteFolderNames = new HashSet(); - HashSet localFolderNames = new HashSet(); + HashMap localFolderMap = new HashMap(); List foldersToCreate = new LinkedList(); LocalStore localStore = account.getLocalStore(); for (Folder localFolder : localFolders) { - localFolderNames.add(localFolder.getRemoteName()); + localFolderMap.put(localFolder.getRemoteName(), localFolder); } // Add any new folders in the remote store to the local store. for (Folder remoteFolder : remoteFolders) { - if (!localFolderNames.contains(remoteFolder.getRemoteName())) { + Folder localFolder = localFolderMap.get(remoteFolder.getRemoteName()); + if (localFolder == null) { foldersToCreate.add(localStore.getFolder(remoteFolder.getRemoteName(), remoteFolder.getName())); + } else { + ((LocalFolder)localFolder).setName(remoteFolder.getName()); } remoteFolderNames.add(remoteFolder.getRemoteName()); } diff --git a/src/com/fsck/k9/mail/Folder.java b/src/com/fsck/k9/mail/Folder.java index 709e76168..9d9c3423b 100644 --- a/src/com/fsck/k9/mail/Folder.java +++ b/src/com/fsck/k9/mail/Folder.java @@ -218,10 +218,6 @@ public abstract class Folder { public Account getAccount() { return mAccount; } - - public boolean isSyncMode() { - return false; - } public String getPushState() { return null; diff --git a/src/com/fsck/k9/mail/store/EasStore.java b/src/com/fsck/k9/mail/store/EasStore.java index a26001678..18da89b83 100644 --- a/src/com/fsck/k9/mail/store/EasStore.java +++ b/src/com/fsck/k9/mail/store/EasStore.java @@ -91,8 +91,9 @@ public class EasStore extends Store { private static final Message[] EMPTY_MESSAGE_ARRAY = new Message[0]; // This key is sent the first time we sync the folder hierarchy, and also the first time - // we sync the items any "collection" (emails in a folder). + // we sync the items any "collection" (i.e., emails in a folder). public static final String INITIAL_SYNC_KEY = "0"; + private static final String NO_PARENT_ID = "0"; private static final String PING_COMMAND = "Ping"; private static final String PROVISION_COMMAND = "Provision"; @@ -812,13 +813,12 @@ public class EasStore extends Store { for (Folder folder : folderList) { EasFolder easFolder = (EasFolder)folder; - // K-9 has its own special Outbox folder that works similar to the one on the - // Exchange server. Adding this one will cause 2 Outbox folders in our list. - if (easFolder.mType != FolderSyncParser.OUTBOX_TYPE) { - mFolderList.put(easFolder.getRemoteName(), easFolder); - easFolder.setSyncKey(syncKeys.get(easFolder.getRemoteName())); - } + mFolderList.put(easFolder.getRemoteName(), easFolder); + easFolder.setSyncKey(syncKeys.get(easFolder.getRemoteName())); } + + // We need to update all folder display names based on parenting. + resolveFolderDisplayNames(mFolderList); } for (Folder folder : folderList) { @@ -892,6 +892,32 @@ public class EasStore extends Store { Log.e(K9.LOG_TAG, "Exception encountered while restoring folders from the local store", e); } } + + private void resolveFolderDisplayNames(HashMap folderMap) { + for (Entry entry : folderMap.entrySet()) { + String parentId = entry.getValue().getParentId(); + if (parentId != null && !parentId.equals(NO_PARENT_ID)) { + resolveOneFolderDisplayName(entry.getValue(), folderMap); + } + } + } + + private void resolveOneFolderDisplayName(EasFolder folder, HashMap folderMap) { + EasFolder parentFolder = folderMap.get(folder.getParentId()); + if (parentFolder != null) { + String parentId = parentFolder.getParentId(); + if (parentId != null && !parentId.equals(NO_PARENT_ID)) { + // Recurse to ensure the folder's name is complete before continuing. + resolveOneFolderDisplayName(parentFolder, folderMap); + } + + String name = parentFolder.getName(); + folder.mName = name + "/" + folder.mName; + // We only need to update the name once, reset the parent Id so we don't waste + // cycles doing it again. + folder.setParentId(null); + } + } @Override public Folder getFolder(String serverId) { @@ -1021,6 +1047,7 @@ public class EasStore extends Store { private String mName; private String mServerId; private int mType; + private String mParentId = null; private boolean mIsOpen = false; private String mSyncKey = null; @@ -1034,6 +1061,14 @@ public class EasStore extends Store { mServerId = serverId; mType = type; } + + public String getParentId() { + return mParentId; + } + + public void setParentId(String parentId) { + mParentId = parentId; + } public String getSyncKey() throws MessagingException { if (mSyncKey == null) { @@ -1047,11 +1082,6 @@ public class EasStore extends Store { mSyncKey = key; } - @Override - public boolean isSyncMode() { - return true; - } - @Override public void open(OpenMode mode) throws MessagingException { mIsOpen = true; diff --git a/src/com/fsck/k9/mail/store/LocalStore.java b/src/com/fsck/k9/mail/store/LocalStore.java index 9e4e40604..797aa4403 100644 --- a/src/com/fsck/k9/mail/store/LocalStore.java +++ b/src/com/fsck/k9/mail/store/LocalStore.java @@ -1240,6 +1240,11 @@ public class LocalStore extends Store implements Serializable { public String getName() { return mName; } + + public void setName(String name) throws MessagingException { + mName = name; + updateFolderColumn("name", mName); + } @Override public boolean exists() throws MessagingException { diff --git a/src/com/fsck/k9/mail/store/exchange/adapter/FolderSyncParser.java b/src/com/fsck/k9/mail/store/exchange/adapter/FolderSyncParser.java index 52ee3fb4f..f22a86fda 100644 --- a/src/com/fsck/k9/mail/store/exchange/adapter/FolderSyncParser.java +++ b/src/com/fsck/k9/mail/store/exchange/adapter/FolderSyncParser.java @@ -19,11 +19,9 @@ package com.fsck.k9.mail.store.exchange.adapter; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import android.content.ContentProviderOperation; import android.util.Log; import com.fsck.k9.K9; @@ -56,26 +54,10 @@ public class FolderSyncParser extends Parser { public static final int USER_MAILBOX_TYPE = 12; public static final List mValidFolderTypes = Arrays.asList(INBOX_TYPE, DRAFTS_TYPE, - DELETED_TYPE, SENT_TYPE, OUTBOX_TYPE, USER_MAILBOX_TYPE);//, CALENDAR_TYPE, CONTACTS_TYPE); - -// public static final String ALL_BUT_ACCOUNT_MAILBOX = MailboxColumns.ACCOUNT_KEY + "=? and " + -// MailboxColumns.TYPE + "!=" + Mailbox.TYPE_EAS_ACCOUNT_MAILBOX; -// -// private static final String WHERE_SERVER_ID_AND_ACCOUNT = MailboxColumns.SERVER_ID + "=? and " + -// MailboxColumns.ACCOUNT_KEY + "=?"; -// -// private static final String WHERE_DISPLAY_NAME_AND_ACCOUNT = MailboxColumns.DISPLAY_NAME + -// "=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; -// -// private static final String WHERE_PARENT_SERVER_ID_AND_ACCOUNT = -// MailboxColumns.PARENT_SERVER_ID +"=? and " + MailboxColumns.ACCOUNT_KEY + "=?"; -// -// private static final String[] MAILBOX_ID_COLUMNS_PROJECTION = -// new String[] {MailboxColumns.ID, MailboxColumns.SERVER_ID}; - -// private long mAccountId; -// private String mAccountIdAsString; -// private String[] mBindArguments = new String[2]; + DELETED_TYPE, SENT_TYPE, USER_MAILBOX_TYPE); + // OUTBOX_TYPE is not included because K-9 uses its own special outbox. Adding the remote folder + // causes duplicate folder names. + private List folderList; private EasStore easStore; @@ -85,15 +67,12 @@ public class FolderSyncParser extends Parser { this.easStore = easStore; this.folderList = folderList; -// mAccountId = mAccount.mId; -// mAccountIdAsString = Long.toString(mAccountId); } @Override public boolean parse() throws IOException { int status; boolean res = false; - boolean resetFolders = false; if (nextTag(START_DOCUMENT) != Tags.FOLDER_FOLDER_SYNC) throw new EasParserException(); while (nextTag(START_DOCUMENT) != END_DOCUMENT) { @@ -104,12 +83,8 @@ public class FolderSyncParser extends Parser { if (status == Eas.FOLDER_STATUS_INVALID_KEY) { easStore.setStoreSyncKey("0"); Log.e(K9.LOG_TAG, "Bad sync key; RESET and delete all folders"); -// mContentResolver.delete(Mailbox.CONTENT_URI, ALL_BUT_ACCOUNT_MAILBOX, -// new String[] {Long.toString(mAccountId)}); -// // Stop existing syncs and reconstruct _main -// SyncManager.stopNonAccountMailboxSyncsForAccount(mAccountId); + // EASTODO res = true; - resetFolders = true; } else { // Other errors are at the server, so let's throw an error that will // cause this sync to be retried at a later time @@ -122,54 +97,27 @@ public class FolderSyncParser extends Parser { userLog("New Account SyncKey: ", easStore.getStoreSyncKey()); } else if (tag == Tags.FOLDER_CHANGES) { changesParser(); - } else + } else { skipTag(); + } } -// synchronized (mService.getSynchronizer()) { -// if (!mService.isStopped() || resetFolders) { -// ContentValues cv = new ContentValues(); -// cv.put(AccountColumns.SYNC_KEY, mAccount.mSyncKey); -// mAccount.update(mContext, cv); -// userLog("Leaving FolderSyncParser with Account syncKey=", mAccount.mSyncKey); -// } -// } return res; } -// private Cursor getServerIdCursor(String serverId) { -// mBindArguments[0] = serverId; -// mBindArguments[1] = mAccountIdAsString; -// return mContentResolver.query(Mailbox.CONTENT_URI, EmailContent.ID_PROJECTION, -// WHERE_SERVER_ID_AND_ACCOUNT, mBindArguments, null); -// } -// - public void deleteParser(ArrayList ops) throws IOException { + public void deleteParser() throws IOException { + /* Right now we only do full refreshes on the folder list. while (nextTag(Tags.FOLDER_DELETE) != END) { switch (tag) { case Tags.FOLDER_SERVER_ID: String serverId = getValue(); -// // Find the mailbox in this account with the given serverId -// Cursor c = getServerIdCursor(serverId); -// try { -// if (c.moveToFirst()) { -// userLog("Deleting ", serverId); -// ops.add(ContentProviderOperation.newDelete( -// ContentUris.withAppendedId(Mailbox.CONTENT_URI, -// c.getLong(0))).build()); -// AttachmentProvider.deleteAllMailboxAttachmentFiles(mContext, -// mAccountId, mMailbox.mId); -// } -// } finally { -// c.close(); -// } break; default: skipTag(); } - } + }*/ } - public void addParser(ArrayList ops) throws IOException { + public void addParser() throws IOException { String name = null; String serverId = null; String parentId = null; @@ -198,61 +146,19 @@ public class FolderSyncParser extends Parser { } } if (mValidFolderTypes.contains(type)) { - Folder folder = easStore.new EasFolder(name, serverId, type); + EasStore.EasFolder folder = easStore.new EasFolder(name, serverId, type); folderList.add(folder); -// Mailbox m = new Mailbox(); -// m.mDisplayName = name; -// m.mServerId = serverId; -// m.mAccountKey = mAccountId; -// m.mType = Mailbox.TYPE_MAIL; -// // Note that all mailboxes default to checking "never" (i.e. manual sync only) -// // We set specific intervals for inbox, contacts, and (eventually) calendar -// m.mSyncInterval = Mailbox.CHECK_INTERVAL_NEVER; -// switch (type) { -// case INBOX_TYPE: -// m.mType = Mailbox.TYPE_INBOX; -// m.mSyncInterval = mAccount.mSyncInterval; -// break; -// case CONTACTS_TYPE: -// m.mType = Mailbox.TYPE_CONTACTS; -// m.mSyncInterval = mAccount.mSyncInterval; -// break; -// case OUTBOX_TYPE: -// // TYPE_OUTBOX mailboxes are known by SyncManager to sync whenever they aren't -// // empty. The value of mSyncFrequency is ignored for this kind of mailbox. -// m.mType = Mailbox.TYPE_OUTBOX; -// break; -// case SENT_TYPE: -// m.mType = Mailbox.TYPE_SENT; -// break; -// case DRAFTS_TYPE: -// m.mType = Mailbox.TYPE_DRAFTS; -// break; -// case DELETED_TYPE: -// m.mType = Mailbox.TYPE_TRASH; -// break; -// case CALENDAR_TYPE: -// m.mType = Mailbox.TYPE_CALENDAR; -// m.mSyncInterval = mAccount.mSyncInterval; -// break; -// } -// -// // Make boxes like Contacts and Calendar invisible in the folder list -// m.mFlagVisible = (m.mType < Mailbox.TYPE_NOT_EMAIL); -// -// if (!parentId.equals("0")) { -// m.mParentServerId = parentId; -// } -// -// userLog("Adding mailbox: ", m.mDisplayName); -// ops.add(ContentProviderOperation -// .newInsert(Mailbox.CONTENT_URI).withValues(m.toContentValues()).build()); + + if (parentId != null) { + folder.setParentId(parentId); + } } return; } - public void updateParser(ArrayList ops) throws IOException { + public void updateParser() throws IOException { + /* Right now we only do full refreshes on the folder list. String serverId = null; String displayName = null; String parentId = null; @@ -272,98 +178,27 @@ public class FolderSyncParser extends Parser { break; } } + // We'll make a change if one of parentId or displayName are specified // serverId is required, but let's be careful just the same if (serverId != null && (displayName != null || parentId != null)) { -// Cursor c = getServerIdCursor(serverId); -// try { -// // If we find the mailbox (using serverId), make the change -// if (c.moveToFirst()) { -// userLog("Updating ", serverId); -// ContentValues cv = new ContentValues(); -// if (displayName != null) { -// cv.put(Mailbox.DISPLAY_NAME, displayName); -// } -// if (parentId != null) { -// cv.put(Mailbox.PARENT_SERVER_ID, parentId); -// } -// ops.add(ContentProviderOperation.newUpdate( -// ContentUris.withAppendedId(Mailbox.CONTENT_URI, -// c.getLong(0))).withValues(cv).build()); -// } -// } finally { -// c.close(); -// } - } + }*/ } public void changesParser() throws IOException { - // Keep track of new boxes, deleted boxes, updated boxes - ArrayList ops = new ArrayList(); - while (nextTag(Tags.FOLDER_CHANGES) != END) { if (tag == Tags.FOLDER_ADD) { - addParser(ops); + addParser(); } else if (tag == Tags.FOLDER_DELETE) { - deleteParser(ops); + deleteParser(); } else if (tag == Tags.FOLDER_UPDATE) { - updateParser(ops); + updateParser(); } else if (tag == Tags.FOLDER_COUNT) { getValueInt(); - } else + } else { skipTag(); + } } - -// // The mock stream is used for junit tests, so that the parsing code can be tested -// // separately from the provider code. -// // TODO Change tests to not require this; remove references to the mock stream -// if (mMock != null) { -// mMock.setResult(null); -// return; -// } - - // Create the new mailboxes in a single batch operation - // Don't save any data if the service has been stopped -// synchronized (mService.getSynchronizer()) { - if (!ops.isEmpty()/* && !mService.isStopped()*/) { - userLog("Applying ", ops.size(), " mailbox operations."); - - // Execute the batch -// try { -// mContentResolver.applyBatch(EmailProvider.EMAIL_AUTHORITY, ops); - userLog("New Account SyncKey: ", easStore.getStoreSyncKey()); -// } catch (RemoteException e) { -// // There is nothing to be done here; fail by returning null -// } catch (OperationApplicationException e) { -// // There is nothing to be done here; fail by returning null -// } - -// // Look for sync issues and its children and delete them -// // I'm not aware of any other way to deal with this properly -// mBindArguments[0] = "Sync Issues"; -// mBindArguments[1] = mAccountIdAsString; -// Cursor c = mContentResolver.query(Mailbox.CONTENT_URI, -// MAILBOX_ID_COLUMNS_PROJECTION, WHERE_DISPLAY_NAME_AND_ACCOUNT, -// mBindArguments, null); -// String parentServerId = null; -// long id = 0; -// try { -// if (c.moveToFirst()) { -// id = c.getLong(0); -// parentServerId = c.getString(1); -// } -// } finally { -// c.close(); -// } -// if (parentServerId != null) { -// mContentResolver.delete(ContentUris.withAppendedId(Mailbox.CONTENT_URI, id), -// null, null); -// mBindArguments[0] = parentServerId; -// mContentResolver.delete(Mailbox.CONTENT_URI, WHERE_PARENT_SERVER_ID_AND_ACCOUNT, -// mBindArguments); -// } - } -// } } void userLog(String ...strings) { @@ -373,5 +208,4 @@ public class FolderSyncParser extends Parser { void userLog(String string, int num, String string2) { Log.i(K9.LOG_TAG, string + num + string2); } - }