[geary/wip/create-folders-713492: 24/26] GenericFolder implements the full IMAP experience



commit 2b765f9d7dba35826215519167706a33e4896017
Author: Charles Lindsay <chaz yorba org>
Date:   Thu Feb 6 17:58:26 2014 -0800

    GenericFolder implements the full IMAP experience
    
    There's a new MinimalFolder that only supports the base operations we
    need *all* folders to have.

 po/POTFILES.in                                     |    5 +-
 src/CMakeLists.txt                                 |    5 +-
 .../gmail/imap-engine-gmail-account.vala           |   16 +-
 .../gmail/imap-engine-gmail-folder.vala            |    2 +-
 .../imap-engine/imap-engine-email-prefetcher.vala  |    4 +-
 .../imap-engine/imap-engine-generic-account.vala   |   46 +-
 .../imap-engine-generic-all-mail-folder.vala       |   18 -
 .../imap-engine-generic-drafts-folder.vala         |   28 -
 .../imap-engine/imap-engine-generic-folder.vala    | 1273 +-------------------
 .../imap-engine-generic-sent-mail-folder.vala      |   22 -
 .../imap-engine-generic-trash-folder.vala          |   23 -
 .../imap-engine/imap-engine-minimal-folder.vala    | 1282 ++++++++++++++++++++
 .../imap-engine/imap-engine-replay-queue.vala      |    4 +-
 .../other/imap-engine-other-account.vala           |   19 +-
 .../other/imap-engine-other-folder.vala            |    7 +-
 .../outlook/imap-engine-outlook-account.vala       |   21 +-
 .../outlook/imap-engine-outlook-folder.vala        |    7 +-
 .../imap-engine-abstract-list-email.vala           |    8 +-
 .../replay-ops/imap-engine-copy-email.vala         |    4 +-
 .../replay-ops/imap-engine-create-email.vala       |    4 +-
 .../replay-ops/imap-engine-fetch-email.vala        |    4 +-
 .../replay-ops/imap-engine-list-email-by-id.vala   |    2 +-
 .../imap-engine-list-email-by-sparse-id.vala       |    2 +-
 .../replay-ops/imap-engine-mark-email.vala         |    4 +-
 .../replay-ops/imap-engine-move-email.vala         |    4 +-
 .../replay-ops/imap-engine-remove-email.vala       |    4 +-
 .../replay-ops/imap-engine-replay-append.vala      |    4 +-
 .../replay-ops/imap-engine-replay-disconnect.vala  |    4 +-
 .../replay-ops/imap-engine-replay-removal.vala     |    4 +-
 .../imap-engine-server-search-email.vala           |    2 +-
 .../yahoo/imap-engine-yahoo-account.vala           |   21 +-
 .../yahoo/imap-engine-yahoo-folder.vala            |    7 +-
 32 files changed, 1355 insertions(+), 1505 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 563fe59..aeefcd2 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -195,11 +195,8 @@ src/engine/imap-engine/imap-engine-contact-store.vala
 src/engine/imap-engine/imap-engine-email-flag-watcher.vala
 src/engine/imap-engine/imap-engine-email-prefetcher.vala
 src/engine/imap-engine/imap-engine-generic-account.vala
-src/engine/imap-engine/imap-engine-generic-all-mail-folder.vala
-src/engine/imap-engine/imap-engine-generic-drafts-folder.vala
 src/engine/imap-engine/imap-engine-generic-folder.vala
-src/engine/imap-engine/imap-engine-generic-sent-mail-folder.vala
-src/engine/imap-engine/imap-engine-generic-trash-folder.vala
+src/engine/imap-engine/imap-engine-minimal-folder.vala
 src/engine/imap-engine/imap-engine-receive-replay-operation.vala
 src/engine/imap-engine/imap-engine-replay-operation.vala
 src/engine/imap-engine/imap-engine-replay-queue.vala
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 26390b2..c824205 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -182,11 +182,8 @@ engine/imap-engine/imap-engine-contact-store.vala
 engine/imap-engine/imap-engine-email-flag-watcher.vala
 engine/imap-engine/imap-engine-email-prefetcher.vala
 engine/imap-engine/imap-engine-generic-account.vala
-engine/imap-engine/imap-engine-generic-all-mail-folder.vala
-engine/imap-engine/imap-engine-generic-drafts-folder.vala
 engine/imap-engine/imap-engine-generic-folder.vala
-engine/imap-engine/imap-engine-generic-sent-mail-folder.vala
-engine/imap-engine/imap-engine-generic-trash-folder.vala
+engine/imap-engine/imap-engine-minimal-folder.vala
 engine/imap-engine/imap-engine-receive-replay-operation.vala
 engine/imap-engine/imap-engine-replay-operation.vala
 engine/imap-engine/imap-engine-replay-queue.vala
diff --git a/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala 
b/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
index bf1e159..d4b0733 100644
--- a/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
+++ b/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
@@ -71,7 +71,7 @@ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount {
         }
     }
     
-    protected override GenericFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
+    protected override MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
         ImapDB.Account local_account, ImapDB.Folder local_folder) {
         // although Gmail supports XLIST, this will be called on startup if the XLIST properties
         // for the folders hasn't been retrieved yet.  Once they've been retrieved and stored in
@@ -81,19 +81,7 @@ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount {
         
         switch (special_folder_type) {
             case SpecialFolderType.ALL_MAIL:
-                return new GenericAllMailFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-            
-            case SpecialFolderType.SENT:
-                return new GenericSentMailFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-            
-            case SpecialFolderType.TRASH:
-                return new GenericTrashFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-            
-            case SpecialFolderType.DRAFTS:
-                return new GenericDraftsFolder(this, remote_account, local_account, local_folder,
+                return new MinimalFolder(this, remote_account, local_account, local_folder,
                     special_folder_type);
             
             default:
diff --git a/src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala 
b/src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala
index 8fe7c07..75776bd 100644
--- a/src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala
+++ b/src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala
@@ -4,7 +4,7 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-private class Geary.ImapEngine.GmailFolder : GenericFolder, FolderSupport.Archive {
+private class Geary.ImapEngine.GmailFolder : MinimalFolder, FolderSupport.Archive {
     public GmailFolder(GmailAccount account, Imap.Account remote, ImapDB.Account local,
         ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
         base (account, remote, local, local_folder, special_folder_type);
diff --git a/src/engine/imap-engine/imap-engine-email-prefetcher.vala 
b/src/engine/imap-engine/imap-engine-email-prefetcher.vala
index 7b8bd3e..1924271 100644
--- a/src/engine/imap-engine/imap-engine-email-prefetcher.vala
+++ b/src/engine/imap-engine/imap-engine-email-prefetcher.vala
@@ -23,7 +23,7 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
     public Nonblocking.CountingSemaphore active_sem { get; private set;
         default = new Nonblocking.CountingSemaphore(null); }
     
-    private unowned ImapEngine.GenericFolder folder;
+    private unowned ImapEngine.MinimalFolder folder;
     private int start_delay_sec;
     private Nonblocking.Mutex mutex = new Nonblocking.Mutex();
     private Gee.TreeSet<Geary.Email> prefetch_emails = new Gee.TreeSet<Geary.Email>(
@@ -31,7 +31,7 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
     private uint schedule_id = 0;
     private Cancellable cancellable = new Cancellable();
     
-    public EmailPrefetcher(ImapEngine.GenericFolder folder, int start_delay_sec = PREFETCH_DELAY_SEC) {
+    public EmailPrefetcher(ImapEngine.MinimalFolder folder, int start_delay_sec = PREFETCH_DELAY_SEC) {
         assert(start_delay_sec > 0);
         
         this.folder = folder;
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala 
b/src/engine/imap-engine/imap-engine-generic-account.vala
index 2f2c880..efc2db8 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -13,8 +13,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
     private Imap.Account remote;
     private ImapDB.Account local;
     private bool open = false;
-    private Gee.HashMap<FolderPath, GenericFolder> folder_map = new Gee.HashMap<
-        FolderPath, GenericFolder>();
+    private Gee.HashMap<FolderPath, MinimalFolder> folder_map = new Gee.HashMap<
+        FolderPath, MinimalFolder>();
     private Gee.HashMap<FolderPath, Folder> local_only = new Gee.HashMap<FolderPath, Folder>();
     private uint refresh_folder_timeout_id = 0;
     private bool in_refresh_enumerate = false;
@@ -194,12 +194,12 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
         message("%s: Rebuild complete", to_string());
     }
     
-    // Subclasses should implement this to return their flavor of a GenericFolder with the
+    // Subclasses should implement this to return their flavor of a MinimalFolder with the
     // appropriate interfaces attached.  The returned folder should have its SpecialFolderType
     // set using either the properties from the local folder or its path.
     //
     // This won't be called to build the Outbox or search folder, but for all others (including Inbox) it 
will.
-    protected abstract GenericFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
+    protected abstract MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
         ImapDB.Account local_account, ImapDB.Folder local_folder);
     
     // Subclasses with specific SearchFolder implementations should override
@@ -208,15 +208,15 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
         return new SearchFolder(this);
     }
     
-    private GenericFolder build_folder(ImapDB.Folder local_folder) {
+    private MinimalFolder build_folder(ImapDB.Folder local_folder) {
         return Geary.Collection.get_first(build_folders(
             Geary.iterate<ImapDB.Folder>(local_folder).to_array_list()));
     }
 
-    private Gee.Collection<GenericFolder> build_folders(Gee.Collection<ImapDB.Folder> local_folders) {
+    private Gee.Collection<MinimalFolder> build_folders(Gee.Collection<ImapDB.Folder> local_folders) {
         Gee.ArrayList<ImapDB.Folder> folders_to_build = new Gee.ArrayList<ImapDB.Folder>();
-        Gee.ArrayList<GenericFolder> built_folders = new Gee.ArrayList<GenericFolder>();
-        Gee.ArrayList<GenericFolder> return_folders = new Gee.ArrayList<GenericFolder>();
+        Gee.ArrayList<MinimalFolder> built_folders = new Gee.ArrayList<MinimalFolder>();
+        Gee.ArrayList<MinimalFolder> return_folders = new Gee.ArrayList<MinimalFolder>();
         
         foreach(ImapDB.Folder local_folder in local_folders) {
             if (folder_map.has_key(local_folder.get_path()))
@@ -226,7 +226,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
         }
         
         foreach(ImapDB.Folder folder_to_build in folders_to_build) {
-            GenericFolder folder = new_folder(folder_to_build.get_path(), remote, local, folder_to_build);
+            MinimalFolder folder = new_folder(folder_to_build.get_path(), remote, local, folder_to_build);
             folder_map.set(folder.path, folder);
             built_folders.add(folder);
             return_folders.add(folder);
@@ -425,7 +425,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
             yield local.clone_folder_async(remote_folder, cancellable);
         }
         
-        // Fetch the local account's version of the folder for the GenericFolder
+        // Fetch the local account's version of the folder for the MinimalFolder
         return build_folder((ImapDB.Folder) yield local.fetch_folder_async(path, cancellable));
     }
     
@@ -470,7 +470,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
         if (folder != null)
             return folder;
         
-        GenericFolder? generic_folder = null;
+        MinimalFolder? minimal_folder = null;
         Geary.FolderPath? path = information.get_special_folder_path(special);
         if (path != null) {
             debug("Previously used %s for special folder %s", path.to_string(), special.to_string());
@@ -515,17 +515,17 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
         if (path in folder_map.keys) {
             debug("Promoting %s to special folder %s", path.to_string(), special.to_string());
             
-            generic_folder = folder_map.get(path);
+            minimal_folder = folder_map.get(path);
         } else {
             debug("Creating %s to use as special folder %s", path.to_string(), special.to_string());
             
             // TODO: ignore error due to already existing.
             yield remote.create_folder_async(path, cancellable);
-            generic_folder = (GenericFolder) yield fetch_folder_async(path, cancellable);
+            minimal_folder = (MinimalFolder) yield fetch_folder_async(path, cancellable);
         }
         
-        generic_folder.set_special_folder_type(special);
-        return generic_folder;
+        minimal_folder.set_special_folder_type(special);
+        return minimal_folder;
     }
     
     public override async Geary.Folder get_required_special_folder_async(Geary.SpecialFolderType special,
@@ -564,17 +564,17 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
         // update all remote folders properties in the local store and active in the system
         Gee.HashSet<Geary.FolderPath> altered_paths = new Gee.HashSet<Geary.FolderPath>();
         foreach (Imap.Folder remote_folder in remote_folders.values) {
-            GenericFolder? generic_folder = existing_folders.get(remote_folder.path)
-                as GenericFolder;
-            if (generic_folder == null)
+            MinimalFolder? minimal_folder = existing_folders.get(remote_folder.path)
+                as MinimalFolder;
+            if (minimal_folder == null)
                 continue;
             
             // only worry about alterations if the remote is openable
             if (remote_folder.properties.is_openable.is_possible()) {
-                ImapDB.Folder local_folder = generic_folder.local_folder;
+                ImapDB.Folder local_folder = minimal_folder.local_folder;
                 
                 if (remote_folder.properties.have_contents_changed(local_folder.get_properties(),
-                    generic_folder.to_string())) {
+                    minimal_folder.to_string())) {
                     altered_paths.add(remote_folder.path);
                 }
             }
@@ -592,8 +592,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
             // (but only promote, not demote, since getting the special folder type via its
             // properties relies on the optional XLIST extension)
             // use this iteration to add discovered properties to map
-            if (generic_folder.special_folder_type == SpecialFolderType.NONE)
-                
generic_folder.set_special_folder_type(remote_folder.properties.attrs.get_special_folder_type());
+            if (minimal_folder.special_folder_type == SpecialFolderType.NONE)
+                
minimal_folder.set_special_folder_type(remote_folder.properties.attrs.get_special_folder_type());
         }
         
         // If path in remote but not local, need to add it
@@ -630,7 +630,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
                 debug("Unable to fetch local folder after cloning: %s", convert_err.message);
             }
         }
-        Gee.Collection<GenericFolder> engine_added = new Gee.ArrayList<Geary.Folder>();
+        Gee.Collection<MinimalFolder> engine_added = new Gee.ArrayList<Geary.Folder>();
         engine_added.add_all(build_folders(folders_to_build));
         
         // TODO: Remove local folders no longer available remotely.
diff --git a/src/engine/imap-engine/imap-engine-generic-folder.vala 
b/src/engine/imap-engine/imap-engine-generic-folder.vala
index 9af8b1d..15e2b9d 100644
--- a/src/engine/imap-engine/imap-engine-generic-folder.vala
+++ b/src/engine/imap-engine/imap-engine-generic-folder.vala
@@ -1,1281 +1,18 @@
-/* Copyright 2011-2013 Yorba Foundation
+/* Copyright 2013 Yorba Foundation
  *
  * This software is licensed under the GNU Lesser General Public License
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.FolderSupport.Copy,
-    Geary.FolderSupport.Mark, Geary.FolderSupport.Move {
-    private const int FORCE_OPEN_REMOTE_TIMEOUT_SEC = 10;
-    
-    public override Account account { get { return _account; } }
-    
-    public override FolderProperties properties { get { return _properties; } }
-    
-    public override FolderPath path {
-        get {
-            return local_folder.get_path();
-        }
-    }
-    
-    private SpecialFolderType _special_folder_type;
-    public override SpecialFolderType special_folder_type {
-        get {
-            return _special_folder_type;
-        }
-    }
-    
-    internal ImapDB.Folder local_folder  { get; protected set; }
-    internal Imap.Folder? remote_folder { get; protected set; default = null; }
-    internal EmailPrefetcher email_prefetcher { get; private set; }
-    internal EmailFlagWatcher email_flag_watcher;
-    
-    private weak GenericAccount _account;
-    private Geary.AggregatedFolderProperties _properties = new Geary.AggregatedFolderProperties(
-        false, false);
-    private Imap.Account remote;
-    private ImapDB.Account local;
-    private Folder.OpenFlags open_flags = OpenFlags.NONE;
-    private int open_count = 0;
-    private bool remote_opened = false;
-    private Nonblocking.ReportingSemaphore<bool>? remote_semaphore = null;
-    private ReplayQueue? replay_queue = null;
-    private int remote_count = -1;
-    private uint open_remote_timer_id = 0;
-    
+private class Geary.ImapEngine.GenericFolder : MinimalFolder, Geary.FolderSupport.Remove {
     public GenericFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local,
         ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
-        _account = account;
-        this.remote = remote;
-        this.local = local;
-        this.local_folder = local_folder;
-        _special_folder_type = special_folder_type;
-        _properties.add(local_folder.get_properties());
-        
-        email_flag_watcher = new EmailFlagWatcher(this);
-        email_flag_watcher.email_flags_changed.connect(on_email_flags_changed);
-        
-        email_prefetcher = new EmailPrefetcher(this);
-        
-        local_folder.email_complete.connect(on_email_complete);
-    }
-    
-    ~EngineFolder() {
-        if (open_count > 0)
-            warning("Folder %s destroyed without closing", to_string());
-        
-        local_folder.email_complete.disconnect(on_email_complete);
-    }
-    
-    public void set_special_folder_type(SpecialFolderType new_type) {
-        SpecialFolderType old_type = _special_folder_type;
-        _special_folder_type = new_type;
-        if(old_type != new_type)
-            notify_special_folder_type_changed(old_type, new_type);
+        base (account, remote, local, local_folder, special_folder_type);
     }
     
-    public override Geary.Folder.OpenState get_open_state() {
-        if (open_count == 0)
-            return Geary.Folder.OpenState.CLOSED;
-        
-        return (remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL;
-    }
-    
-    // Returns the synchronized remote count (-1 if not opened) and the last seen remote count (stored
-    // locally, -1 if not available)
-    //
-    // Return value is the remote_count, unless the remote is unopened, in which case it's the
-    // last_seen_remote_count (which may be -1).
-    //
-    // remote_count, last_seen_remote_count, and returned value do not reflect any notion of
-    // messages marked for removal
-    internal int get_remote_counts(out int remote_count, out int last_seen_remote_count) {
-        remote_count = this.remote_count;
-        last_seen_remote_count = local_folder.get_properties().select_examine_messages;
-        if (last_seen_remote_count < 0)
-            last_seen_remote_count = local_folder.get_properties().status_messages;
-        
-        return (remote_count >= 0) ? remote_count : last_seen_remote_count;
-    }
-    
-    private async bool normalize_folders(Geary.Imap.Folder remote_folder, Geary.Folder.OpenFlags open_flags,
-        Cancellable? cancellable) throws Error {
-        debug("%s: Begin normalizing remote and local folders", to_string());
-        
-        Geary.Imap.FolderProperties local_properties = local_folder.get_properties();
-        Geary.Imap.FolderProperties remote_properties = remote_folder.properties;
-        
-        // and both must have their next UID's (it's possible they don't if it's a non-selectable
-        // folder)
-        if (local_properties.uid_next == null || local_properties.uid_validity == null) {
-            debug("%s: Unable to verify UIDs: missing local UIDNEXT (%s) and/or UIDVALIDITY (%s)",
-                to_string(), (local_properties.uid_next == null).to_string(),
-                (local_properties.uid_validity == null).to_string());
-            
-            return false;
-        }
-        
-        if (remote_properties.uid_next == null || remote_properties.uid_validity == null) {
-            debug("%s: Unable to verify UIDs: missing remote UIDNEXT (%s) and/or UIDVALIDITY (%s)",
-                to_string(), (remote_properties.uid_next == null).to_string(),
-                (remote_properties.uid_validity == null).to_string());
-            
-            return false;
-        }
-        
-        // If UIDVALIDITY changes, all email in the folder must be removed as the UIDs are now
-        // invalid ... we merely detach the emails (leaving their contents behind) so duplicate
-        // detection can fix them up.  But once all UIDs are removed, it's much like the next
-        // if case where no earliest UID available, so simply exit.
-        //
-        // see http://tools.ietf.org/html/rfc3501#section-2.3.1.1
-        if (local_properties.uid_validity.value != remote_properties.uid_validity.value) {
-            debug("%s: UID validity changed, detaching all email: %s -> %s", to_string(),
-                local_properties.uid_validity.value.to_string(),
-                remote_properties.uid_validity.value.to_string());
-            
-            yield local_folder.detach_all_emails_async(cancellable);
-            
-            return true;
-        }
-        
-        // fetch email from earliest email to last to (a) remove any deletions and (b) update
-        // any flags that may have changed
-        ImapDB.EmailIdentifier? local_earliest_id = yield local_folder.get_earliest_id_async(cancellable);
-        ImapDB.EmailIdentifier? local_latest_id = yield local_folder.get_latest_id_async(cancellable);
-        
-        // verify still open; this is required throughout after each yield, as a close_async() can
-        // come in ay any time since this does not run in the context of open_async()
-        check_open("normalize_folders (local earliest/latest UID)");
-        
-        // if no earliest UID, that means no messages in local store, so nothing to update
-        if (local_earliest_id == null || local_latest_id == null) {
-            debug("%s: local store empty, nothing to normalize", to_string());
-            
-            return true;
-        }
-        
-        assert(local_earliest_id.has_uid());
-        assert(local_latest_id.has_uid());
-        
-        // if any messages are still marked for removal from last time, that means the EXPUNGE
-        // never arrived from the server, in which case the folder is "dirty" and needs a full
-        // normalization
-        Gee.Set<ImapDB.EmailIdentifier>? already_marked_ids = yield local_folder.get_marked_ids_async(
-            cancellable);
-        
-        // however, there may be enqueue ReplayOperations waiting to remove messages on the server
-        // that marked some or all of those messages
-        Gee.HashSet<ImapDB.EmailIdentifier> to_be_removed = new Gee.HashSet<ImapDB.EmailIdentifier>();
-        replay_queue.get_ids_to_be_remote_removed(to_be_removed);
-        
-        // don't consider those already marked as "already marked" if they were not leftover from
-        // the last open of this folder
-        if (already_marked_ids != null)
-            already_marked_ids.remove_all(to_be_removed);
-        
-        bool is_dirty = (already_marked_ids != null && already_marked_ids.size > 0);
-        
-        if (is_dirty)
-            debug("%s: %d remove markers found, folder is dirty", to_string(), already_marked_ids.size);
-        
-        // if UIDNEXT has changed, that indicates messages have been appended (and possibly removed)
-        int64 uidnext_diff = remote_properties.uid_next.value - local_properties.uid_next.value;
-        
-        int local_message_count = (local_properties.select_examine_messages >= 0)
-            ? local_properties.select_examine_messages : 0;
-        int remote_message_count = (remote_properties.select_examine_messages >= 0)
-            ? remote_properties.select_examine_messages : 0;
-        
-        // if UIDNEXT is the same as last time AND the total count of email is the same, then
-        // nothing has been added or removed
-        if (!is_dirty && uidnext_diff == 0 && local_message_count == remote_message_count) {
-            debug("%s: No messages added/removed since last opened, normalization completed", to_string());
-            
-            return true;
-        }
-        
-        // a full normalize works from the highest possible UID on the remote and work down to the lowest 
UID on
-        // the local; this covers all messages appended since last seen as well as any removed
-        Imap.UID last_uid = remote_properties.uid_next.previous(true);
-        
-        // if the difference in UIDNEXT values equals the difference in message count, then only
-        // an append could have happened, so only pull in the new messages ... note that this is not 
foolproof,
-        // as UIDs are not guaranteed to increase by 1; however, this is a standard implementation practice,
-        // so it's worth looking for
-        //
-        // (Also, this cannot fail; if this situation exists, then it cannot by definition indicate another
-        // situation, esp. messages being removed.)
-        Imap.UID first_uid;
-        if (!is_dirty && uidnext_diff == (remote_message_count - local_message_count)) {
-            first_uid = local_latest_id.uid.next(true);
-            
-            debug("%s: Messages only appended (local/remote UIDNEXT=%s/%s total=%d/%d diff=%s), gathering 
mail UIDs %s:%s",
-                to_string(), local_properties.uid_next.to_string(), remote_properties.uid_next.to_string(),
-                local_properties.select_examine_messages, remote_properties.select_examine_messages, 
uidnext_diff.to_string(),
-                first_uid.to_string(), last_uid.to_string());
-        } else {
-            first_uid = local_earliest_id.uid;
-            
-            debug("%s: Messages appended/removed (local/remote UIDNEXT=%s/%s total=%d/%d diff=%s), gathering 
mail UIDs %s:%s",
-                to_string(), local_properties.uid_next.to_string(), remote_properties.uid_next.to_string(),
-                local_properties.select_examine_messages, remote_properties.select_examine_messages, 
uidnext_diff.to_string(),
-                first_uid.to_string(), last_uid.to_string());
-        }
-        
-        // get all the UIDs in said range from the local store, sorted; convert to non-null
-        // for ease of use later
-        Gee.Set<Imap.UID>? local_uids = yield local_folder.list_uids_by_range_async(
-            first_uid, last_uid, true, cancellable);
-        if (local_uids == null)
-            local_uids = new Gee.HashSet<Imap.UID>();
-        
-        check_open("normalize_folders (list local)");
-        
-        // Do the same on the remote ... make non-null for ease of use later
-        Gee.Set<Imap.UID>? remote_uids = yield remote_folder.list_uids_async(
-            new Imap.MessageSet.uid_range(first_uid, last_uid), cancellable);
-        if (remote_uids == null)
-            remote_uids = new Gee.HashSet<Imap.UID>();
-        
-        check_open("normalize_folders (list remote)");
-        
-        debug("%s: Loaded local (%d) and remote (%d) UIDs, normalizing...", to_string(),
-            local_uids.size, remote_uids.size);
-        
-        Gee.HashSet<Imap.UID> removed_uids = new Gee.HashSet<Imap.UID>();
-        Gee.HashSet<Imap.UID> appended_uids = new Gee.HashSet<Imap.UID>();
-        Gee.HashSet<Imap.UID> inserted_uids = new Gee.HashSet<Imap.UID>();
-        
-        // Because the number of UIDs being processed can be immense in large folders, process
-        // in a background thread
-        yield Nonblocking.Concurrent.global.schedule_async(() => {
-            // walk local UIDs looking for UIDs no longer on remote, removing those that are available
-            // make the next pass that much shorter
-            foreach (Imap.UID local_uid in local_uids) {
-                // if in local but not remote, consider removed from remote
-                if (!remote_uids.remove(local_uid))
-                    removed_uids.add(local_uid);
-            }
-            
-            // everything remaining in remote has been added since folder last seen ... whether they're
-            // discovered (inserted) or appended depends on the highest local UID
-            foreach (Imap.UID remote_uid in remote_uids) {
-                if (remote_uid.compare_to(local_latest_id.uid) > 0)
-                    appended_uids.add(remote_uid);
-                else
-                    inserted_uids.add(remote_uid);
-            }
-            
-            // the UIDs marked for removal are going to be re-inserted into the vector once they're
-            // cleared, so add them here as well
-            if (already_marked_ids != null) {
-                foreach (ImapDB.EmailIdentifier id in already_marked_ids) {
-                    assert(id.has_uid());
-                    
-                    if (!appended_uids.contains(id.uid))
-                        inserted_uids.add(id.uid);
-                }
-            }
-        }, cancellable);
-        
-        debug("%s: changes since last seen: removed=%d appended=%d inserted=%d", to_string(),
-            removed_uids.size, appended_uids.size, inserted_uids.size);
-        
-        // fetch from the server the local store's required flags for all appended/inserted messages
-        // (which is simply equal to all remaining remote UIDs)
-        Gee.List<Geary.Email>? to_create = null;
-        if (remote_uids.size > 0) {
-            // for new messages, get the local store's required fields (which provide duplicate
-            // detection)
-            to_create = yield remote_folder.list_email_async(
-                new Imap.MessageSet.uid_sparse(remote_uids.to_array()), ImapDB.Folder.REQUIRED_FIELDS,
-                cancellable);
-        }
-        
-        check_open("normalize_folders (list remote appended/inserted required fields)");
-        
-        // store new messages and add IDs to the appended/discovered EmailIdentifier buckets
-        Gee.Set<ImapDB.EmailIdentifier> appended_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
-        Gee.Set<ImapDB.EmailIdentifier> locally_appended_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
-        Gee.Set<ImapDB.EmailIdentifier> inserted_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
-        Gee.Set<ImapDB.EmailIdentifier> locally_inserted_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
-        if (to_create != null && to_create.size > 0) {
-            Gee.Map<Email, bool>? created_or_merged = yield local_folder.create_or_merge_email_async(
-                to_create, cancellable);
-            assert(created_or_merged != null);
-            
-            // it's possible a large number of messages have come in, so process them in the
-            // background
-            yield Nonblocking.Concurrent.global.schedule_async(() => {
-                foreach (Email email in created_or_merged.keys) {
-                    ImapDB.EmailIdentifier id = (ImapDB.EmailIdentifier) email.id;
-                    bool created = created_or_merged.get(email);
-                    
-                    // report all appended email, but separate out email never seen before (created)
-                    // as locally-appended
-                    if (appended_uids.contains(id.uid)) {
-                        appended_ids.add(id);
-                        
-                        if (created)
-                            locally_appended_ids.add(id);
-                    } else if (inserted_uids.contains(id.uid)) {
-                        inserted_ids.add(id);
-                        
-                        if (created)
-                            locally_inserted_ids.add(id);
-                    }
-                }
-            }, cancellable);
-            
-            debug("%s: Finished creating/merging %d emails", to_string(), created_or_merged.size);
-        }
-        
-        check_open("normalize_folders (created/merged appended/inserted emails)");
-        
-        // Convert removed UIDs into EmailIdentifiers and detach immediately
-        Gee.Set<ImapDB.EmailIdentifier>? removed_ids = null;
-        if (removed_uids.size > 0) {
-            removed_ids = yield local_folder.get_ids_async(removed_uids,
-                ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
-            if (removed_ids != null && removed_ids.size > 0) {
-                yield local_folder.detach_multiple_emails_async(removed_ids, cancellable);
-            }
-        }
-        
-        check_open("normalize_folders (removed emails)");
-        
-        // remove any extant remove markers, as everything is accounted for now, except for those
-        // waiting to be removed in the queue
-        yield local_folder.clear_remove_markers_async(to_be_removed, cancellable);
-        
-        check_open("normalize_folders (clear remove markers)");
-        
-        //
-        // now normalized
-        // notify subscribers of changes
-        //
-        
-        Folder.CountChangeReason count_change_reason = Folder.CountChangeReason.NONE;
-        
-        if (removed_ids != null && removed_ids.size > 0) {
-            // there may be operations pending on the remote queue for these removed emails; notify
-            // operations that the email has shuffled off this mortal coil
-            replay_queue.notify_remote_removed_ids(removed_ids);
-            
-            // notify subscribers about emails that have been removed
-            debug("%s: Notifying of %d removed emails since last opened", to_string(), removed_ids.size);
-            notify_email_removed(removed_ids);
-            
-            count_change_reason |= Folder.CountChangeReason.REMOVED;
-        }
-        
-        // notify inserted (new email located somewhere inside the local vector)
-        if (inserted_ids.size > 0) {
-            debug("%s: Notifying of %d inserted emails since last opened", to_string(), inserted_ids.size);
-            notify_email_inserted(inserted_ids);
-            
-            count_change_reason |= Folder.CountChangeReason.INSERTED;
-        }
-        
-        // notify inserted (new email located somewhere inside the local vector that had to be
-        // created, i.e. no portion was stored locally)
-        if (locally_inserted_ids.size > 0) {
-            debug("%s: Notifying of %d locally inserted emails since last opened", to_string(),
-                locally_inserted_ids.size);
-            notify_email_locally_inserted(locally_inserted_ids);
-            
-            count_change_reason |= Folder.CountChangeReason.INSERTED;
-        }
-        
-        // notify appended (new email added since the folder was last opened)
-        if (appended_ids.size > 0) {
-            debug("%s: Notifying of %d appended emails since last opened", to_string(), appended_ids.size);
-            notify_email_appended(appended_ids);
-            
-            count_change_reason |= Folder.CountChangeReason.APPENDED;
-        }
-        
-        // notify locally appended (new email never seen before added since the folder was last
-        // opened)
-        if (locally_appended_ids.size > 0) {
-            debug("%s: Notifying of %d locally appended emails since last opened", to_string(),
-                locally_appended_ids.size);
-            notify_email_locally_appended(locally_appended_ids);
-            
-            count_change_reason |= Folder.CountChangeReason.APPENDED;
-        }
-        
-        if (count_change_reason != Folder.CountChangeReason.NONE) {
-            debug("%s: Notifying of %Xh count change reason (%d remote messages)", to_string(),
-                count_change_reason, remote_message_count);
-            notify_email_count_changed(remote_message_count, count_change_reason);
-        }
-        
-        debug("%s: Completed normalize_folder", to_string());
-        
-        return true;
-    }
-    
-    public override async void wait_for_open_async(Cancellable? cancellable = null) throws Error {
-        if (open_count == 0 || remote_semaphore == null)
-            throw new EngineError.OPEN_REQUIRED("wait_for_open_async() can only be called after 
open_async()");
-        
-        // if remote has not yet been opened, do it now ... this bool can go true only once after
-        // an open_async, it's reset at close time
-        if (!remote_opened) {
-            debug("wait_for_open_async %s: opening remote on demand...", to_string());
-            
-            remote_opened = true;
-            open_remote_async.begin(open_flags, null);
-        }
-        
-        if (!yield remote_semaphore.wait_for_result_async(cancellable))
-            throw new EngineError.ALREADY_CLOSED("%s failed to open", to_string());
-    }
-    
-    public override async bool open_async(Geary.Folder.OpenFlags open_flags, Cancellable? cancellable = null)
-        throws Error {
-        if (open_count++ > 0) {
-            // even if opened or opening, respect the NO_DELAY flag
-            if (open_flags.is_all_set(OpenFlags.NO_DELAY)) {
-                cancel_remote_open_timer();
-                wait_for_open_async.begin();
-            }
-            
-            debug("Not opening %s: already open (open_count=%d)", to_string(), open_count);
-            
-            return false;
-        }
-        
-        this.open_flags = open_flags;
-        
-        open_internal(open_flags, cancellable);
-        
-        return true;
-    }
-    
-    private void open_internal(Folder.OpenFlags open_flags, Cancellable? cancellable) {
-        remote_semaphore = new Geary.Nonblocking.ReportingSemaphore<bool>(false);
-        
-        // start the replay queue
-        replay_queue = new ReplayQueue(this);
-        
-        // Unless NO_DELAY is set, do NOT open the remote side here; wait for the ReplayQueue to
-        // require a remote connection or wait_for_open_async() to be called ... this allows for
-        // fast local-only operations to occur, local-only either because (a) the folder has all
-        // the information required (for a list or fetch operation), or (b) the operation was de
-        // facto local-only.  In particular, EmailStore will open and close lots of folders,
-        // causing a lot of connection setup and teardown
-        //
-        // However, want to eventually open, otherwise if there's no user interaction (i.e. a
-        // second account Inbox they don't manipulate), no remote connection will ever be made,
-        // meaning that folder normalization never happens and unsolicited notifications never
-        // arrive
-        if (open_flags.is_all_set(OpenFlags.NO_DELAY))
-            wait_for_open_async.begin();
-        else
-            start_remote_open_timer();
-    }
-    
-    private void start_remote_open_timer() {
-        if (open_remote_timer_id != 0)
-            Source.remove(open_remote_timer_id);
-        
-        open_remote_timer_id = Timeout.add_seconds(FORCE_OPEN_REMOTE_TIMEOUT_SEC, on_open_remote_timeout);
-    }
-    
-    private void cancel_remote_open_timer() {
-        if (open_remote_timer_id == 0)
-            return;
-        
-        Source.remove(open_remote_timer_id);
-        open_remote_timer_id = 0;
-    }
-    
-    private bool on_open_remote_timeout() {
-        open_remote_timer_id = 0;
-        
-        // remote was not forced open due to caller, so open now
-        wait_for_open_async.begin();
-        
-        return false;
-    }
-    
-    private async void open_remote_async(Geary.Folder.OpenFlags open_flags, Cancellable? cancellable) {
-        cancel_remote_open_timer();
-        
-        // watch for folder closing before this call got a chance to execute
-        if (open_count == 0)
-            return;
-        
-        try {
-            debug("Fetching information for remote folder %s", to_string());
-            Imap.Folder folder = yield remote.fetch_folder_async(local_folder.get_path(),
-                cancellable);
-            
-            debug("Opening remote folder %s", folder.to_string());
-            yield folder.open_async(cancellable);
-            
-            // allow subclasses to examine the opened folder and resolve any vital
-            // inconsistencies
-            if (yield normalize_folders(folder, open_flags, cancellable)) {
-                // update flags, properties, etc.
-                yield local.update_folder_select_examine_async(folder, cancellable);
-                
-                // signals
-                folder.appended.connect(on_remote_appended);
-                folder.removed.connect(on_remote_removed);
-                folder.disconnected.connect(on_remote_disconnected);
-            
-                // state
-                remote_count = folder.properties.email_total;
-                
-                // all set; bless the remote folder as opened
-                remote_folder = folder;
-            } else {
-                debug("Unable to prepare remote folder %s: normalize_folders() failed", to_string());
-                notify_open_failed(Geary.Folder.OpenFailed.REMOTE_FAILED, null);
-                
-                // schedule immediate close
-                close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR, cancellable);
-                
-                return;
-            }
-        } catch (Error open_err) {
-            debug("Unable to open or prepare remote folder %s: %s", to_string(), open_err.message);
-            notify_open_failed(Geary.Folder.OpenFailed.REMOTE_FAILED, open_err);
-            
-            // schedule immediate close
-            close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR, cancellable);
-            
-            return;
-        }
-        
-        int count;
-        try {
-            count = (remote_folder != null)
-                ? remote_count
-                : yield local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE, cancellable);
-        } catch (Error count_err) {
-            debug("Unable to fetch count from local folder: %s", count_err.message);
-            
-            count = 0;
-        }
-        
-        // notify any threads of execution waiting for the remote folder to open that the result
-        // of that operation is ready
-        try {
-            remote_semaphore.notify_result(remote_folder != null, null);
-        } catch (Error notify_err) {
-            debug("Unable to fire semaphore notifying remote folder ready/not ready: %s",
-                notify_err.message);
-            
-            // do this now rather than wait for close_internal_async() to execute to ensure that
-            // any replay operations already queued don't attempt to run
-            clear_remote_folder();
-            
-            notify_open_failed(Geary.Folder.OpenFailed.REMOTE_FAILED, notify_err);
-            
-            // schedule immediate close
-            close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR, cancellable);
-            
-            return;
-        }
-        
-        _properties.add(remote_folder.properties);
-        
-        // notify any subscribers with similar information
-        notify_opened(
-            (remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL,
-            count);
-    }
-    
-    public override async void close_async(Cancellable? cancellable = null) throws Error {
-        if (open_count == 0 || --open_count > 0)
-            return;
-        
-        if (remote_folder != null)
-            _properties.remove(remote_folder.properties);
-        
-        yield close_internal_async(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, cancellable);
-    }
-    
-    // NOTE: This bypasses open_count and forces the Folder closed.
-    internal async void close_internal_async(Folder.CloseReason local_reason, Folder.CloseReason 
remote_reason,
-        Cancellable? cancellable) {
-        cancel_remote_open_timer();
-        
-        // only flushing pending ReplayOperations if this is a "clean" close, not forced due to
-        // error
-        bool flush_pending = !remote_reason.is_error();
-        
-        // If closing due to error, notify all operations waiting for the remote that it's not
-        // coming available ... this wakes up any ReplayOperation blocking on wait_for_open_async(),
-        // necessary in order to finish ReplayQueue.close_async (i.e. to prevent deadlock); this
-        // is necessary because it's possible for this method to be called before the remote_folder
-        // has even had a chance to open.
-        //
-        // Note that we don't want to do this for a clean close, because we want to flush out
-        // pending operations first
-        Imap.Folder? closing_remote_folder = null;
-        if (!flush_pending)
-            closing_remote_folder = clear_remote_folder();
-        
-        // Close the replay queues; if a "clean" close, flush pending operations so everything
-        // gets a chance to run; if forced close, drop everything outstanding
-        try {
-            if (replay_queue != null) {
-                debug("Closing replay queue for %s... (flush_pending=%s)", to_string(),
-                    flush_pending.to_string());
-                yield replay_queue.close_async(flush_pending);
-                debug("Closed replay queue for %s", to_string());
-            }
-        } catch (Error replay_queue_err) {
-            debug("Error closing %s replay queue: %s", to_string(), replay_queue_err.message);
-        }
-        
-        replay_queue = null;
-        
-        // if a "clean" close, now go ahead and close the folder
-        if (flush_pending)
-            closing_remote_folder = clear_remote_folder();
-        
-        if (closing_remote_folder != null) {
-            // to avoid keeping the caller waiting while the remote end closes (i.e. drops the
-            // connection or performs an IMAP CLOSE operation), close it in the background and
-            // reestablish connection there, if necessary
-            //
-            // TODO: Problem with this is that we cannot effectively signal or report a close error,
-            // because by the time this operation completes the folder is considered closed.  That
-            // may not be important to most callers, however.
-            //
-            // It also means the reference to the Folder must be maintained until completely
-            // closed.  Also not a problem, as GenericAccount does that internally.  However, this
-            // might be an issue if GenericAccount removes this folder due to a user command or
-            // detection on the server, so this background op keeps a reference to the Folder
-            close_remote_folder_async.begin(this, closing_remote_folder, remote_reason);
-        }
-        
-        remote_opened = false;
-        
-        // if remote reason is an error, then close_remote_folder_async() will be performing
-        // reestablishment, so go no further
-        if (remote_reason.is_error())
-            return;
-        
-        // forced closed one way or another
-        open_count = 0;
-        
-        // use remote_reason even if remote_folder was null; it could be that the error occurred
-        // while opening and remote_folder was yet unassigned ... also, need to call this every
-        // time, even if remote was not fully opened, as some callers rely on order of signals
-        notify_closed(remote_reason);
-        
-        // see above note for why this must be called every time
-        notify_closed(local_reason);
-        
-        notify_closed(CloseReason.FOLDER_CLOSED);
-        
-        debug("Folder %s closed", to_string());
-    }
-    
-    // Returns the remote_folder, if it was set
-    private Imap.Folder? clear_remote_folder() {
-        if (remote_folder != null) {
-            // disconnect signals before ripping out reference
-            remote_folder.appended.disconnect(on_remote_appended);
-            remote_folder.removed.disconnect(on_remote_removed);
-            remote_folder.disconnected.disconnect(on_remote_disconnected);
-        }
-        
-        Imap.Folder? old_remote_folder = remote_folder;
-        remote_folder = null;
-        remote_count = -1;
-        
-        remote_semaphore.reset();
-        try {
-            remote_semaphore.notify_result(false, null);
-        } catch (Error err) {
-            debug("Error attempting to notify that remote folder %s is now closed: %s", to_string(),
-                err.message);
-        }
-        
-        return old_remote_folder;
-    }
-    
-    // See note in close_async() for why this method is static and uses an owned ref
-    private static async void close_remote_folder_async(owned GenericFolder folder,
-        owned Imap.Folder remote_folder, Folder.CloseReason remote_reason) {
-        // force the remote closed; if due to a remote disconnect and plan on reopening, *still*
-        // need to do this
-        try {
-            yield remote_folder.close_async(null);
-        } catch (Error err) {
-            debug("Unable to close remote %s: %s", remote_folder.to_string(), err.message);
-            
-            // fallthrough
-        }
-        
-        // reestablish connection (which requires renormalizing the remote with the local) if
-        // close was in error
-        if (remote_reason.is_error()) {
-            debug("Reestablishing broken connection to %s", folder.to_string());
-            folder.open_internal(OpenFlags.NO_DELAY, null);
-        }
-    }
-    
-    public override async void find_boundaries_async(Gee.Collection<Geary.EmailIdentifier> ids,
-        out Geary.EmailIdentifier? low, out Geary.EmailIdentifier? high,
+    public async void remove_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
         Cancellable? cancellable = null) throws Error {
-        low = null;
-        high = null;
-        
-        Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath>? map
-            = yield account.get_containing_folders_async(ids, cancellable);
-        
-        if (map != null) {
-            Gee.ArrayList<Geary.EmailIdentifier> in_folder = new Gee.ArrayList<Geary.EmailIdentifier>();
-            foreach (Geary.EmailIdentifier id in map.get_keys()) {
-                if (path in map.get(id))
-                    in_folder.add(id);
-            }
-            
-            if (in_folder.size > 0) {
-                Gee.SortedSet<Geary.EmailIdentifier> sorted = Geary.EmailIdentifier.sort(in_folder);
-                
-                low = sorted.first();
-                high = sorted.last();
-            }
-        }
-    }
-    
-    private void on_email_complete(Gee.Collection<Geary.EmailIdentifier> email_ids) {
-        notify_email_locally_complete(email_ids);
-    }
-    
-    private void on_remote_appended(int reported_remote_count) {
-        debug("%s on_remote_appended: remote_count=%d reported_remote_count=%d", to_string(), remote_count,
-            reported_remote_count);
-        
-        if (reported_remote_count < 0)
-            return;
-        
-        // from the new remote total and the old remote total, glean the SequenceNumbers of the
-        // new email(s)
-        Gee.List<Imap.SequenceNumber> positions = new Gee.ArrayList<Imap.SequenceNumber>();
-        for (int pos = remote_count + 1; pos <= reported_remote_count; pos++)
-            positions.add(new Imap.SequenceNumber(pos));
-        
-        // store the remote count NOW, as further appended messages could arrive before the
-        // ReplayAppend executes
-        remote_count = reported_remote_count;
-        
-        if (positions.size > 0)
-            replay_queue.schedule_server_notification(new ReplayAppend(this, reported_remote_count, 
positions));
-    }
-    
-    // Need to prefetch at least an EmailIdentifier (and duplicate detection fields) to create a
-    // normalized placeholder in the local database of the message, so all positions are
-    // properly relative to the end of the message list; once this is done, notify user of new
-    // messages.  If duplicates, create_email_async() will fall through to an updated merge,
-    // which is exactly what we want.
-    //
-    // This MUST only be called from ReplayAppend.
-    internal async void do_replay_appended_messages(int reported_remote_count,
-        Gee.List<Imap.SequenceNumber> remote_positions) {
-        StringBuilder positions_builder = new StringBuilder("( ");
-        foreach (Imap.SequenceNumber remote_position in remote_positions)
-            positions_builder.append_printf("%s ", remote_position.to_string());
-        positions_builder.append(")");
-        
-        debug("%s do_replay_appended_message: current remote_count=%d reported_remote_count=%d 
remote_positions=%s",
-            to_string(), remote_count, reported_remote_count, positions_builder.str);
-        
-        if (remote_positions.size == 0)
-            return;
-        
-        Gee.HashSet<Geary.EmailIdentifier> created = new Gee.HashSet<Geary.EmailIdentifier>();
-        Gee.HashSet<Geary.EmailIdentifier> appended = new Gee.HashSet<Geary.EmailIdentifier>();
-        try {
-            // If remote doesn't fully open, then don't fire signal, as we'll be unable to
-            // normalize the folder
-            if (!yield remote_semaphore.wait_for_result_async(null)) {
-                debug("%s do_replay_appended_message: remote never opened", to_string());
-                
-                return;
-            }
-            
-            Imap.MessageSet msg_set = new Imap.MessageSet.sparse(remote_positions.to_array());
-            Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(msg_set,
-                ImapDB.Folder.REQUIRED_FIELDS, null);
-            if (list != null && list.size > 0) {
-                debug("%s do_replay_appended_message: %d new messages in %s", to_string(),
-                    list.size, msg_set.to_string());
-                
-                // need to report both if it was created (not known before) and appended (which
-                // could mean created or simply a known email associated with this folder)
-                Gee.Map<Geary.Email, bool> created_or_merged =
-                    yield local_folder.create_or_merge_email_async(list, null);
-                foreach (Geary.Email email in created_or_merged.keys) {
-                    // true means created
-                    if (created_or_merged.get(email)) {
-                        debug("%s do_replay_appended_message: appended email ID %s added",
-                            to_string(), email.id.to_string());
-                        
-                        created.add(email.id);
-                    } else {
-                        debug("%s do_replay_appended_message: appended email ID %s associated",
-                            to_string(), email.id.to_string());
-                    }
-                    
-                    appended.add(email.id);
-                }
-            } else {
-                debug("%s do_replay_appended_message: no new messages in %s", to_string(),
-                    msg_set.to_string());
-            }
-        } catch (Error err) {
-            debug("%s do_replay_appended_message: Unable to process: %s",
-                to_string(), err.message);
-        }
-        
-        // store the reported count, *not* the current count (which is updated outside the of
-        // the queue) to ensure that updates happen serially and reflect committed local changes
-        try {
-            yield local_folder.update_remote_selected_message_count(reported_remote_count, null);
-        } catch (Error err) {
-            debug("%s do_replay_appended_message: Unable to save appended remote count %d: %s",
-                to_string(), reported_remote_count, err.message);
-        }
-        
-        if (appended.size > 0)
-            notify_email_appended(appended);
-        
-        if (created.size > 0)
-            notify_email_locally_appended(created);
-        
-        notify_email_count_changed(reported_remote_count, CountChangeReason.APPENDED);
-        
-        debug("%s do_replay_appended_message: completed, current remote_count=%d reported_remote_count=%d",
-            to_string(), remote_count, reported_remote_count);
-    }
-    
-    private void on_remote_removed(Imap.SequenceNumber position, int reported_remote_count) {
-        debug("%s on_remote_removed: remote_count=%d position=%s reported_remote_count=%d", to_string(),
-            remote_count, position.to_string(), reported_remote_count);
-        
-        if (reported_remote_count < 0)
-            return;
-        
-        // notify of removal to all pending replay operations
-        replay_queue.notify_remote_removed_position(position);
-        
-        // update remote count NOW, as further appended and removed messages can arrive before
-        // ReplayRemoval executes
-        //
-        // something to note at this point: the ExpungeEmail operation marks messages as removed,
-        // then signals they're removed and reports an adjusted count in its replay_local_async().
-        // remote_count is *not* updated, which is why it's safe to do that here without worry.
-        // similarly, signals are only fired here if marked, so the same EmailIdentifier isn't
-        // reported twice
-        remote_count = reported_remote_count;
-        
-        replay_queue.schedule_server_notification(new ReplayRemoval(this, reported_remote_count, position));
-    }
-    
-    // This MUST only be called from ReplayRemoval.
-    internal async void do_replay_removed_message(int reported_remote_count, Imap.SequenceNumber 
remote_position) {
-        debug("%s do_replay_removed_message: current remote_count=%d remote_position=%d 
reported_remote_count=%d",
-            to_string(), remote_count, remote_position.value, reported_remote_count);
-        
-        if (!remote_position.is_valid()) {
-            debug("%s do_replay_removed_message: ignoring, invalid remote position or count",
-                to_string());
-            
-            return;
-        }
-        
-        int local_count = -1;
-        int local_position = -1;
-        
-        ImapDB.EmailIdentifier? owned_id = null;
-        try {
-            // need total count, including those marked for removal, to accurately calculate position
-            // from server's point of view, not client's
-            local_count = yield local_folder.get_email_count_async(
-                ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null);
-            local_position = remote_position.value - (reported_remote_count + 1 - local_count);
-            
-            // zero or negative means the message exists beyond the local vector's range, so
-            // nothing to do there
-            if (local_position > 0) {
-                debug("%s do_replay_removed_message: local_count=%d local_position=%d", to_string(),
-                    local_count, local_position);
-                
-                owned_id = yield local_folder.get_id_at_async(local_position, null);
-            } else {
-                debug("%s do_replay_removed_message: message not stored locally (local_count=%d 
local_position=%d)",
-                    to_string(), local_count, local_position);
-            }
-        } catch (Error err) {
-            debug("%s do_replay_removed_message: unable to determine ID of removed message %s: %s",
-                to_string(), remote_position.to_string(), err.message);
-        }
-        
-        bool marked = false;
-        if (owned_id != null) {
-            debug("%s do_replay_removed_message: detaching from local store Email ID %s", to_string(),
-                owned_id.to_string());
-            try {
-                // Reflect change in the local store and notify subscribers
-                yield local_folder.detach_single_email_async(owned_id, out marked, null);
-            } catch (Error err) {
-                debug("%s do_replay_removed_message: unable to remove message #%s: %s", to_string(),
-                    remote_position.to_string(), err.message);
-            }
-            
-            // Notify queued replay operations that the email has been removed (by EmailIdentifier)
-            replay_queue.notify_remote_removed_ids(
-                Geary.iterate<ImapDB.EmailIdentifier>(owned_id).to_array_list());
-        } else {
-            debug("%s do_replay_removed_message: remote_position=%d unknown in local store "
-                + "(reported_remote_count=%d local_position=%d local_count=%d)",
-                to_string(), remote_position.value, reported_remote_count, local_position, local_count);
-        }
-        
-        // for debugging
-        int new_local_count = -1;
-        try {
-            new_local_count = yield local_folder.get_email_count_async(
-                ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null);
-        } catch (Error err) {
-            debug("%s do_replay_removed_message: error fetching new local count: %s", to_string(),
-                err.message);
-        }
-        
-        // as with on_remote_appended(), only update in local store inside a queue operation, to
-        // ensure serial commits
-        try {
-            yield local_folder.update_remote_selected_message_count(reported_remote_count, null);
-        } catch (Error err) {
-            debug("%s do_replay_removed_message: unable to save removed remote count: %s", to_string(),
-                err.message);
-        }
-        
-        // notify of change
-        if (!marked && owned_id != null)
-            notify_email_removed(Geary.iterate<Geary.EmailIdentifier>(owned_id).to_array_list());
-        
-        if (!marked)
-            notify_email_count_changed(reported_remote_count, CountChangeReason.REMOVED);
-        
-        debug("%s do_replay_remove_message: completed, current remote_count=%d "
-            + "(reported_remote_count=%d local_count=%d starting local_count=%d remote_position=%d 
local_position=%d marked=%s)",
-            to_string(), remote_count, reported_remote_count, new_local_count, local_count, 
remote_position.value,
-            local_position, marked.to_string());
-    }
-    
-    private void on_remote_disconnected(Imap.ClientSession.DisconnectReason reason) {
-        debug("on_remote_disconnected: reason=%s", reason.to_string());
-        
-        replay_queue.schedule(new ReplayDisconnect(this, reason));
-    }
-    
-    //
-    // list_email_by_id variants
-    //
-    
-    public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier? initial_id,
-        int count, Geary.Email.Field required_fields, Folder.ListFlags flags,
-        Cancellable? cancellable = null) throws Error {
-        Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
-        yield do_list_email_by_id_async("list_email_by_id_async", initial_id, count, required_fields,
-            flags, accumulator, null, cancellable);
-        
-        return !accumulator.is_empty ? accumulator : null;
-    }
-    
-    public override void lazy_list_email_by_id(Geary.EmailIdentifier? initial_id, int count,
-        Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb,
-        Cancellable? cancellable = null) {
-        do_lazy_list_email_by_id_async.begin(initial_id, count, required_fields, flags, cb, cancellable);
-    }
-    
-    private async void do_lazy_list_email_by_id_async(Geary.EmailIdentifier? initial_id, int count,
-        Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? 
cancellable) {
-        try {
-            yield do_list_email_by_id_async("lazy_list_email_by_id", initial_id, count, required_fields,
-                flags, null, cb, cancellable);
-        } catch (Error err) {
-            cb(null, err);
-        }
-    }
-    
-    private async void do_list_email_by_id_async(string method, Geary.EmailIdentifier? initial_id,
-        int count, Geary.Email.Field required_fields, Folder.ListFlags flags,
-        Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable) throws Error {
-        check_open(method);
-        check_flags(method, flags);
-        if (initial_id != null)
-            check_id(method, initial_id);
-        
-        if (count == 0) {
-            // signal finished
-            if (cb != null)
-                cb(null, null);
-            
-            return;
-        }
-        
-        // Schedule list operation and wait for completion.
-        ListEmailByID op = new ListEmailByID(this, (ImapDB.EmailIdentifier) initial_id, count,
-            required_fields, flags, accumulator, cb, cancellable);
-        replay_queue.schedule(op);
-        
-        yield op.wait_for_ready_async(cancellable);
-    }
-    
-    //
-    // list_email_by_sparse_id variants
-    //
-    
-    public async override Gee.List<Geary.Email>? list_email_by_sparse_id_async(
-        Gee.Collection<Geary.EmailIdentifier> ids, Geary.Email.Field required_fields, Folder.ListFlags flags,
-        Cancellable? cancellable = null) throws Error {
-        Gee.ArrayList<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
-        yield do_list_email_by_sparse_id_async("list_email_by_sparse_id_async", ids, required_fields,
-            flags, accumulator, null, cancellable);
-        
-        return (accumulator.size > 0) ? accumulator : null;
-    }
-    
-    public override void lazy_list_email_by_sparse_id(Gee.Collection<Geary.EmailIdentifier> ids,
-        Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? 
cancellable = null) {
-        do_lazy_list_email_by_sparse_id_async.begin(ids, required_fields, flags, cb, cancellable);
-    }
-    
-    private async void do_lazy_list_email_by_sparse_id_async(Gee.Collection<Geary.EmailIdentifier> ids,
-        Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? 
cancellable) {
-        try {
-            yield do_list_email_by_sparse_id_async("lazy_list_email_by_sparse_id", ids, required_fields,
-                flags, null, cb, cancellable);
-        } catch (Error err) {
-            cb(null, err);
-        }
-    }
-    
-    private async void do_list_email_by_sparse_id_async(string method,
-        Gee.Collection<Geary.EmailIdentifier> ids, Geary.Email.Field required_fields, Folder.ListFlags flags,
-        Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable = null) throws Error 
{
-        check_open(method);
-        check_flags(method, flags);
-        check_ids(method, ids);
-        
-        if (ids.size == 0) {
-            // signal finished
-            if (cb != null)
-                cb(null, null);
-            
-            return;
-        }
-        
-        // Schedule list operation and wait for completion.
-        // TODO: Break up requests to avoid hogging the queue
-        ListEmailBySparseID op = new ListEmailBySparseID(this, (Gee.Collection<ImapDB.EmailIdentifier>) ids,
-            required_fields, flags, accumulator, cb, cancellable);
-        replay_queue.schedule(op);
-        
-        yield op.wait_for_ready_async(cancellable);
-    }
-    
-    public override async Gee.Map<Geary.EmailIdentifier, Geary.Email.Field>? list_local_email_fields_async(
-        Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error {
-        check_open("list_local_email_fields_async");
-        check_ids("list_local_email_fields_async", ids);
-        
-        return yield local_folder.list_email_fields_by_id_async(
-            (Gee.Collection<Geary.ImapDB.EmailIdentifier>) ids, ImapDB.Folder.ListFlags.NONE, cancellable);
-    }
-    
-    public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
-        Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable = null)
-        throws Error {
-        check_open("fetch_email_async");
-        check_flags("fetch_email_async", flags);
-        check_id("fetch_email_async", id);
-        
-        FetchEmail op = new FetchEmail(this, (ImapDB.EmailIdentifier) id, required_fields, flags,
-            cancellable);
-        replay_queue.schedule(op);
-        
-        yield op.wait_for_ready_async(cancellable);
-        
-        if (op.email == null) {
-            throw new EngineError.NOT_FOUND("Email %s not found in %s", id.to_string(), to_string());
-        } else if (!op.email.fields.fulfills(required_fields)) {
-            throw new EngineError.INCOMPLETE_MESSAGE("Email %s in %s does not fulfill required fields %Xh 
(has %Xh)",
-                id.to_string(), to_string(), required_fields, op.email.fields);
-        }
-        
-        return op.email;
-    }
-    
-    // Helper function for child classes dealing with the delete/archive question.  This method will
-    // mark the message as deleted and expunge it.
-    protected async void expunge_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
-        Cancellable? cancellable = null) throws Error {
-        check_open("expunge_email_async");
-        check_ids("expunge_email_async", email_ids);
-        
-        RemoveEmail remove = new RemoveEmail(this, (Gee.List<ImapDB.EmailIdentifier>) email_ids,
-            cancellable);
-        replay_queue.schedule(remove);
-        
-        yield remove.wait_for_ready_async(cancellable);
-    }
-    
-    private void check_open(string method) throws EngineError {
-        if (open_count == 0)
-            throw new EngineError.OPEN_REQUIRED("%s failed: folder %s is not open", method, to_string());
-    }
-    
-    private void check_flags(string method, Folder.ListFlags flags) throws EngineError {
-        if (flags.is_all_set(Folder.ListFlags.LOCAL_ONLY) && 
flags.is_all_set(Folder.ListFlags.FORCE_UPDATE)) {
-            throw new EngineError.BAD_PARAMETERS("%s %s failed: LOCAL_ONLY and FORCE_UPDATE are mutually 
exclusive",
-                to_string(), method);
-        }
-    }
-    
-    private void check_id(string method, EmailIdentifier id) throws EngineError {
-        if (!(id is ImapDB.EmailIdentifier))
-            throw new EngineError.BAD_PARAMETERS("Email ID %s is not IMAP Email ID", id.to_string());
-    }
-    
-    private void check_ids(string method, Gee.Collection<EmailIdentifier> ids) throws EngineError {
-        foreach (EmailIdentifier id in ids)
-            check_id(method, id);
-    }
-    
-    public virtual async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
-        Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, 
-        Cancellable? cancellable = null) throws Error {
-        check_open("mark_email_async");
-        
-        MarkEmail mark = new MarkEmail(this, to_mark, flags_to_add, flags_to_remove, cancellable);
-        replay_queue.schedule(mark);
-        yield mark.wait_for_ready_async(cancellable);
-    }
-
-    public virtual async void copy_email_async(Gee.List<Geary.EmailIdentifier> to_copy,
-        Geary.FolderPath destination, Cancellable? cancellable = null) throws Error {
-        check_open("copy_email_async");
-        check_ids("copy_email_async", to_copy);
-        
-        CopyEmail copy = new CopyEmail(this, (Gee.List<ImapDB.EmailIdentifier>) to_copy, destination);
-        replay_queue.schedule(copy);
-        yield copy.wait_for_ready_async(cancellable);
-    }
-
-    public virtual async void move_email_async(Gee.List<Geary.EmailIdentifier> to_move,
-        Geary.FolderPath destination, Cancellable? cancellable = null) throws Error {
-        check_open("move_email_async");
-        check_ids("move_email_async", to_move);
-        
-        MoveEmail move = new MoveEmail(this, (Gee.List<ImapDB.EmailIdentifier>) to_move, destination);
-        replay_queue.schedule(move);
-        yield move.wait_for_ready_async(cancellable);
-    }
-    
-    private void on_email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> changed) {
-        notify_email_flags_changed(changed);
-    }
-    
-    // TODO: A proper public search mechanism; note that this always round-trips to the remote,
-    // doesn't go through the replay queue, and doesn't deal with messages marked for deletion
-    internal async Geary.EmailIdentifier? find_earliest_email_async(DateTime datetime,
-        Geary.EmailIdentifier? before_id, Cancellable? cancellable) throws Error {
-        check_open("find_earliest_email_async");
-        if (before_id != null)
-            check_id("find_earliest_email_async", before_id);
-        
-        Imap.SearchCriteria criteria = new Imap.SearchCriteria();
-        criteria.is_(Imap.SearchCriterion.since_internaldate(new 
Imap.InternalDate.from_date_time(datetime)));
-        
-        // if before_id available, only search for messages before it
-        if (before_id != null) {
-            Imap.UID? before_uid = yield local_folder.get_uid_async((ImapDB.EmailIdentifier) before_id,
-                ImapDB.Folder.ListFlags.NONE, cancellable);
-            if (before_uid == null) {
-                throw new EngineError.NOT_FOUND("before_id %s not found in %s", before_id.to_string(),
-                    to_string());
-            }
-            
-            criteria.and(Imap.SearchCriterion.message_set(
-                new Imap.MessageSet.uid_range(new Imap.UID(Imap.UID.MIN), before_uid.previous(true))));
-        }
-        
-        Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
-        ServerSearchEmail op = new ServerSearchEmail(this, criteria, Geary.Email.Field.NONE,
-            accumulator, cancellable);
-        
-        // need to check again due to the yield in the above conditional block
-        check_open("find_earliest_email_async.schedule operation");
-        
-        replay_queue.schedule(op);
-        
-        if (!yield op.wait_for_ready_async(cancellable))
-            return null;
-        
-        // find earliest ID; because all Email comes from Folder, UID should always be present
-        ImapDB.EmailIdentifier? earliest_id = null;
-        foreach (Geary.Email email in accumulator) {
-            ImapDB.EmailIdentifier email_id = (ImapDB.EmailIdentifier) email.id;
-            
-            if (earliest_id == null || email_id.uid.compare_to(earliest_id.uid) < 0)
-                earliest_id = email_id;
-        }
-        
-        return earliest_id;
-    }
-    
-    internal async Geary.EmailIdentifier create_email_async(RFC822.Message rfc822, Geary.EmailFlags? flags,
-        DateTime? date_received, Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error {
-        check_open("create_email_async");
-        if (id != null)
-            check_id("create_email_async", id);
-        
-        Error? cancel_error = null;
-        Geary.EmailIdentifier? ret = null;
-        try {
-            CreateEmail create = new CreateEmail(this, rfc822, flags, date_received, cancellable);
-            replay_queue.schedule(create);
-            yield create.wait_for_ready_async(cancellable);
-            
-            ret = create.created_id;
-        } catch (Error e) {
-            if (e is IOError.CANCELLED)
-                cancel_error = e;
-            else
-                throw e;
-        }
-        
-        Geary.FolderSupport.Remove? remove_folder = this as Geary.FolderSupport.Remove;
-        
-        // Remove old message.
-        if (id != null && remove_folder != null)
-            yield remove_folder.remove_single_email_async(id, null);
-        
-        // If the user cancelled the operation, throw the error here.
-        if (cancel_error != null)
-            throw cancel_error;
-        
-        // If the caller cancelled during the remove operation, delete the newly created message to
-        // safely back out.
-        if (cancellable != null && cancellable.is_cancelled() && ret != null && remove_folder != null)
-            yield remove_folder.remove_single_email_async(ret, null);
-        
-        return ret;
+        yield expunge_email_async(email_ids, cancellable);
     }
 }
 
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
new file mode 100644
index 0000000..9c96996
--- /dev/null
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -0,0 +1,1282 @@
+/* Copyright 2011-2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.FolderSupport.Copy,
+    Geary.FolderSupport.Mark, Geary.FolderSupport.Move, Geary.FolderSupport.Create {
+    private const int FORCE_OPEN_REMOTE_TIMEOUT_SEC = 10;
+    
+    public override Account account { get { return _account; } }
+    
+    public override FolderProperties properties { get { return _properties; } }
+    
+    public override FolderPath path {
+        get {
+            return local_folder.get_path();
+        }
+    }
+    
+    private SpecialFolderType _special_folder_type;
+    public override SpecialFolderType special_folder_type {
+        get {
+            return _special_folder_type;
+        }
+    }
+    
+    internal ImapDB.Folder local_folder  { get; protected set; }
+    internal Imap.Folder? remote_folder { get; protected set; default = null; }
+    internal EmailPrefetcher email_prefetcher { get; private set; }
+    internal EmailFlagWatcher email_flag_watcher;
+    
+    private weak GenericAccount _account;
+    private Geary.AggregatedFolderProperties _properties = new Geary.AggregatedFolderProperties(
+        false, false);
+    private Imap.Account remote;
+    private ImapDB.Account local;
+    private Folder.OpenFlags open_flags = OpenFlags.NONE;
+    private int open_count = 0;
+    private bool remote_opened = false;
+    private Nonblocking.ReportingSemaphore<bool>? remote_semaphore = null;
+    private ReplayQueue? replay_queue = null;
+    private int remote_count = -1;
+    private uint open_remote_timer_id = 0;
+    
+    public MinimalFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local,
+        ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
+        _account = account;
+        this.remote = remote;
+        this.local = local;
+        this.local_folder = local_folder;
+        _special_folder_type = special_folder_type;
+        _properties.add(local_folder.get_properties());
+        
+        email_flag_watcher = new EmailFlagWatcher(this);
+        email_flag_watcher.email_flags_changed.connect(on_email_flags_changed);
+        
+        email_prefetcher = new EmailPrefetcher(this);
+        
+        local_folder.email_complete.connect(on_email_complete);
+    }
+    
+    ~EngineFolder() {
+        if (open_count > 0)
+            warning("Folder %s destroyed without closing", to_string());
+        
+        local_folder.email_complete.disconnect(on_email_complete);
+    }
+    
+    public void set_special_folder_type(SpecialFolderType new_type) {
+        SpecialFolderType old_type = _special_folder_type;
+        _special_folder_type = new_type;
+        if(old_type != new_type)
+            notify_special_folder_type_changed(old_type, new_type);
+    }
+    
+    public override Geary.Folder.OpenState get_open_state() {
+        if (open_count == 0)
+            return Geary.Folder.OpenState.CLOSED;
+        
+        return (remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL;
+    }
+    
+    // Returns the synchronized remote count (-1 if not opened) and the last seen remote count (stored
+    // locally, -1 if not available)
+    //
+    // Return value is the remote_count, unless the remote is unopened, in which case it's the
+    // last_seen_remote_count (which may be -1).
+    //
+    // remote_count, last_seen_remote_count, and returned value do not reflect any notion of
+    // messages marked for removal
+    internal int get_remote_counts(out int remote_count, out int last_seen_remote_count) {
+        remote_count = this.remote_count;
+        last_seen_remote_count = local_folder.get_properties().select_examine_messages;
+        if (last_seen_remote_count < 0)
+            last_seen_remote_count = local_folder.get_properties().status_messages;
+        
+        return (remote_count >= 0) ? remote_count : last_seen_remote_count;
+    }
+    
+    private async bool normalize_folders(Geary.Imap.Folder remote_folder, Geary.Folder.OpenFlags open_flags,
+        Cancellable? cancellable) throws Error {
+        debug("%s: Begin normalizing remote and local folders", to_string());
+        
+        Geary.Imap.FolderProperties local_properties = local_folder.get_properties();
+        Geary.Imap.FolderProperties remote_properties = remote_folder.properties;
+        
+        // and both must have their next UID's (it's possible they don't if it's a non-selectable
+        // folder)
+        if (local_properties.uid_next == null || local_properties.uid_validity == null) {
+            debug("%s: Unable to verify UIDs: missing local UIDNEXT (%s) and/or UIDVALIDITY (%s)",
+                to_string(), (local_properties.uid_next == null).to_string(),
+                (local_properties.uid_validity == null).to_string());
+            
+            return false;
+        }
+        
+        if (remote_properties.uid_next == null || remote_properties.uid_validity == null) {
+            debug("%s: Unable to verify UIDs: missing remote UIDNEXT (%s) and/or UIDVALIDITY (%s)",
+                to_string(), (remote_properties.uid_next == null).to_string(),
+                (remote_properties.uid_validity == null).to_string());
+            
+            return false;
+        }
+        
+        // If UIDVALIDITY changes, all email in the folder must be removed as the UIDs are now
+        // invalid ... we merely detach the emails (leaving their contents behind) so duplicate
+        // detection can fix them up.  But once all UIDs are removed, it's much like the next
+        // if case where no earliest UID available, so simply exit.
+        //
+        // see http://tools.ietf.org/html/rfc3501#section-2.3.1.1
+        if (local_properties.uid_validity.value != remote_properties.uid_validity.value) {
+            debug("%s: UID validity changed, detaching all email: %s -> %s", to_string(),
+                local_properties.uid_validity.value.to_string(),
+                remote_properties.uid_validity.value.to_string());
+            
+            yield local_folder.detach_all_emails_async(cancellable);
+            
+            return true;
+        }
+        
+        // fetch email from earliest email to last to (a) remove any deletions and (b) update
+        // any flags that may have changed
+        ImapDB.EmailIdentifier? local_earliest_id = yield local_folder.get_earliest_id_async(cancellable);
+        ImapDB.EmailIdentifier? local_latest_id = yield local_folder.get_latest_id_async(cancellable);
+        
+        // verify still open; this is required throughout after each yield, as a close_async() can
+        // come in ay any time since this does not run in the context of open_async()
+        check_open("normalize_folders (local earliest/latest UID)");
+        
+        // if no earliest UID, that means no messages in local store, so nothing to update
+        if (local_earliest_id == null || local_latest_id == null) {
+            debug("%s: local store empty, nothing to normalize", to_string());
+            
+            return true;
+        }
+        
+        assert(local_earliest_id.has_uid());
+        assert(local_latest_id.has_uid());
+        
+        // if any messages are still marked for removal from last time, that means the EXPUNGE
+        // never arrived from the server, in which case the folder is "dirty" and needs a full
+        // normalization
+        Gee.Set<ImapDB.EmailIdentifier>? already_marked_ids = yield local_folder.get_marked_ids_async(
+            cancellable);
+        
+        // however, there may be enqueue ReplayOperations waiting to remove messages on the server
+        // that marked some or all of those messages
+        Gee.HashSet<ImapDB.EmailIdentifier> to_be_removed = new Gee.HashSet<ImapDB.EmailIdentifier>();
+        replay_queue.get_ids_to_be_remote_removed(to_be_removed);
+        
+        // don't consider those already marked as "already marked" if they were not leftover from
+        // the last open of this folder
+        if (already_marked_ids != null)
+            already_marked_ids.remove_all(to_be_removed);
+        
+        bool is_dirty = (already_marked_ids != null && already_marked_ids.size > 0);
+        
+        if (is_dirty)
+            debug("%s: %d remove markers found, folder is dirty", to_string(), already_marked_ids.size);
+        
+        // if UIDNEXT has changed, that indicates messages have been appended (and possibly removed)
+        int64 uidnext_diff = remote_properties.uid_next.value - local_properties.uid_next.value;
+        
+        int local_message_count = (local_properties.select_examine_messages >= 0)
+            ? local_properties.select_examine_messages : 0;
+        int remote_message_count = (remote_properties.select_examine_messages >= 0)
+            ? remote_properties.select_examine_messages : 0;
+        
+        // if UIDNEXT is the same as last time AND the total count of email is the same, then
+        // nothing has been added or removed
+        if (!is_dirty && uidnext_diff == 0 && local_message_count == remote_message_count) {
+            debug("%s: No messages added/removed since last opened, normalization completed", to_string());
+            
+            return true;
+        }
+        
+        // a full normalize works from the highest possible UID on the remote and work down to the lowest 
UID on
+        // the local; this covers all messages appended since last seen as well as any removed
+        Imap.UID last_uid = remote_properties.uid_next.previous(true);
+        
+        // if the difference in UIDNEXT values equals the difference in message count, then only
+        // an append could have happened, so only pull in the new messages ... note that this is not 
foolproof,
+        // as UIDs are not guaranteed to increase by 1; however, this is a standard implementation practice,
+        // so it's worth looking for
+        //
+        // (Also, this cannot fail; if this situation exists, then it cannot by definition indicate another
+        // situation, esp. messages being removed.)
+        Imap.UID first_uid;
+        if (!is_dirty && uidnext_diff == (remote_message_count - local_message_count)) {
+            first_uid = local_latest_id.uid.next(true);
+            
+            debug("%s: Messages only appended (local/remote UIDNEXT=%s/%s total=%d/%d diff=%s), gathering 
mail UIDs %s:%s",
+                to_string(), local_properties.uid_next.to_string(), remote_properties.uid_next.to_string(),
+                local_properties.select_examine_messages, remote_properties.select_examine_messages, 
uidnext_diff.to_string(),
+                first_uid.to_string(), last_uid.to_string());
+        } else {
+            first_uid = local_earliest_id.uid;
+            
+            debug("%s: Messages appended/removed (local/remote UIDNEXT=%s/%s total=%d/%d diff=%s), gathering 
mail UIDs %s:%s",
+                to_string(), local_properties.uid_next.to_string(), remote_properties.uid_next.to_string(),
+                local_properties.select_examine_messages, remote_properties.select_examine_messages, 
uidnext_diff.to_string(),
+                first_uid.to_string(), last_uid.to_string());
+        }
+        
+        // get all the UIDs in said range from the local store, sorted; convert to non-null
+        // for ease of use later
+        Gee.Set<Imap.UID>? local_uids = yield local_folder.list_uids_by_range_async(
+            first_uid, last_uid, true, cancellable);
+        if (local_uids == null)
+            local_uids = new Gee.HashSet<Imap.UID>();
+        
+        check_open("normalize_folders (list local)");
+        
+        // Do the same on the remote ... make non-null for ease of use later
+        Gee.Set<Imap.UID>? remote_uids = yield remote_folder.list_uids_async(
+            new Imap.MessageSet.uid_range(first_uid, last_uid), cancellable);
+        if (remote_uids == null)
+            remote_uids = new Gee.HashSet<Imap.UID>();
+        
+        check_open("normalize_folders (list remote)");
+        
+        debug("%s: Loaded local (%d) and remote (%d) UIDs, normalizing...", to_string(),
+            local_uids.size, remote_uids.size);
+        
+        Gee.HashSet<Imap.UID> removed_uids = new Gee.HashSet<Imap.UID>();
+        Gee.HashSet<Imap.UID> appended_uids = new Gee.HashSet<Imap.UID>();
+        Gee.HashSet<Imap.UID> inserted_uids = new Gee.HashSet<Imap.UID>();
+        
+        // Because the number of UIDs being processed can be immense in large folders, process
+        // in a background thread
+        yield Nonblocking.Concurrent.global.schedule_async(() => {
+            // walk local UIDs looking for UIDs no longer on remote, removing those that are available
+            // make the next pass that much shorter
+            foreach (Imap.UID local_uid in local_uids) {
+                // if in local but not remote, consider removed from remote
+                if (!remote_uids.remove(local_uid))
+                    removed_uids.add(local_uid);
+            }
+            
+            // everything remaining in remote has been added since folder last seen ... whether they're
+            // discovered (inserted) or appended depends on the highest local UID
+            foreach (Imap.UID remote_uid in remote_uids) {
+                if (remote_uid.compare_to(local_latest_id.uid) > 0)
+                    appended_uids.add(remote_uid);
+                else
+                    inserted_uids.add(remote_uid);
+            }
+            
+            // the UIDs marked for removal are going to be re-inserted into the vector once they're
+            // cleared, so add them here as well
+            if (already_marked_ids != null) {
+                foreach (ImapDB.EmailIdentifier id in already_marked_ids) {
+                    assert(id.has_uid());
+                    
+                    if (!appended_uids.contains(id.uid))
+                        inserted_uids.add(id.uid);
+                }
+            }
+        }, cancellable);
+        
+        debug("%s: changes since last seen: removed=%d appended=%d inserted=%d", to_string(),
+            removed_uids.size, appended_uids.size, inserted_uids.size);
+        
+        // fetch from the server the local store's required flags for all appended/inserted messages
+        // (which is simply equal to all remaining remote UIDs)
+        Gee.List<Geary.Email>? to_create = null;
+        if (remote_uids.size > 0) {
+            // for new messages, get the local store's required fields (which provide duplicate
+            // detection)
+            to_create = yield remote_folder.list_email_async(
+                new Imap.MessageSet.uid_sparse(remote_uids.to_array()), ImapDB.Folder.REQUIRED_FIELDS,
+                cancellable);
+        }
+        
+        check_open("normalize_folders (list remote appended/inserted required fields)");
+        
+        // store new messages and add IDs to the appended/discovered EmailIdentifier buckets
+        Gee.Set<ImapDB.EmailIdentifier> appended_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
+        Gee.Set<ImapDB.EmailIdentifier> locally_appended_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
+        Gee.Set<ImapDB.EmailIdentifier> inserted_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
+        Gee.Set<ImapDB.EmailIdentifier> locally_inserted_ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
+        if (to_create != null && to_create.size > 0) {
+            Gee.Map<Email, bool>? created_or_merged = yield local_folder.create_or_merge_email_async(
+                to_create, cancellable);
+            assert(created_or_merged != null);
+            
+            // it's possible a large number of messages have come in, so process them in the
+            // background
+            yield Nonblocking.Concurrent.global.schedule_async(() => {
+                foreach (Email email in created_or_merged.keys) {
+                    ImapDB.EmailIdentifier id = (ImapDB.EmailIdentifier) email.id;
+                    bool created = created_or_merged.get(email);
+                    
+                    // report all appended email, but separate out email never seen before (created)
+                    // as locally-appended
+                    if (appended_uids.contains(id.uid)) {
+                        appended_ids.add(id);
+                        
+                        if (created)
+                            locally_appended_ids.add(id);
+                    } else if (inserted_uids.contains(id.uid)) {
+                        inserted_ids.add(id);
+                        
+                        if (created)
+                            locally_inserted_ids.add(id);
+                    }
+                }
+            }, cancellable);
+            
+            debug("%s: Finished creating/merging %d emails", to_string(), created_or_merged.size);
+        }
+        
+        check_open("normalize_folders (created/merged appended/inserted emails)");
+        
+        // Convert removed UIDs into EmailIdentifiers and detach immediately
+        Gee.Set<ImapDB.EmailIdentifier>? removed_ids = null;
+        if (removed_uids.size > 0) {
+            removed_ids = yield local_folder.get_ids_async(removed_uids,
+                ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, cancellable);
+            if (removed_ids != null && removed_ids.size > 0) {
+                yield local_folder.detach_multiple_emails_async(removed_ids, cancellable);
+            }
+        }
+        
+        check_open("normalize_folders (removed emails)");
+        
+        // remove any extant remove markers, as everything is accounted for now, except for those
+        // waiting to be removed in the queue
+        yield local_folder.clear_remove_markers_async(to_be_removed, cancellable);
+        
+        check_open("normalize_folders (clear remove markers)");
+        
+        //
+        // now normalized
+        // notify subscribers of changes
+        //
+        
+        Folder.CountChangeReason count_change_reason = Folder.CountChangeReason.NONE;
+        
+        if (removed_ids != null && removed_ids.size > 0) {
+            // there may be operations pending on the remote queue for these removed emails; notify
+            // operations that the email has shuffled off this mortal coil
+            replay_queue.notify_remote_removed_ids(removed_ids);
+            
+            // notify subscribers about emails that have been removed
+            debug("%s: Notifying of %d removed emails since last opened", to_string(), removed_ids.size);
+            notify_email_removed(removed_ids);
+            
+            count_change_reason |= Folder.CountChangeReason.REMOVED;
+        }
+        
+        // notify inserted (new email located somewhere inside the local vector)
+        if (inserted_ids.size > 0) {
+            debug("%s: Notifying of %d inserted emails since last opened", to_string(), inserted_ids.size);
+            notify_email_inserted(inserted_ids);
+            
+            count_change_reason |= Folder.CountChangeReason.INSERTED;
+        }
+        
+        // notify inserted (new email located somewhere inside the local vector that had to be
+        // created, i.e. no portion was stored locally)
+        if (locally_inserted_ids.size > 0) {
+            debug("%s: Notifying of %d locally inserted emails since last opened", to_string(),
+                locally_inserted_ids.size);
+            notify_email_locally_inserted(locally_inserted_ids);
+            
+            count_change_reason |= Folder.CountChangeReason.INSERTED;
+        }
+        
+        // notify appended (new email added since the folder was last opened)
+        if (appended_ids.size > 0) {
+            debug("%s: Notifying of %d appended emails since last opened", to_string(), appended_ids.size);
+            notify_email_appended(appended_ids);
+            
+            count_change_reason |= Folder.CountChangeReason.APPENDED;
+        }
+        
+        // notify locally appended (new email never seen before added since the folder was last
+        // opened)
+        if (locally_appended_ids.size > 0) {
+            debug("%s: Notifying of %d locally appended emails since last opened", to_string(),
+                locally_appended_ids.size);
+            notify_email_locally_appended(locally_appended_ids);
+            
+            count_change_reason |= Folder.CountChangeReason.APPENDED;
+        }
+        
+        if (count_change_reason != Folder.CountChangeReason.NONE) {
+            debug("%s: Notifying of %Xh count change reason (%d remote messages)", to_string(),
+                count_change_reason, remote_message_count);
+            notify_email_count_changed(remote_message_count, count_change_reason);
+        }
+        
+        debug("%s: Completed normalize_folder", to_string());
+        
+        return true;
+    }
+    
+    public override async void wait_for_open_async(Cancellable? cancellable = null) throws Error {
+        if (open_count == 0 || remote_semaphore == null)
+            throw new EngineError.OPEN_REQUIRED("wait_for_open_async() can only be called after 
open_async()");
+        
+        // if remote has not yet been opened, do it now ... this bool can go true only once after
+        // an open_async, it's reset at close time
+        if (!remote_opened) {
+            debug("wait_for_open_async %s: opening remote on demand...", to_string());
+            
+            remote_opened = true;
+            open_remote_async.begin(open_flags, null);
+        }
+        
+        if (!yield remote_semaphore.wait_for_result_async(cancellable))
+            throw new EngineError.ALREADY_CLOSED("%s failed to open", to_string());
+    }
+    
+    public override async bool open_async(Geary.Folder.OpenFlags open_flags, Cancellable? cancellable = null)
+        throws Error {
+        if (open_count++ > 0) {
+            // even if opened or opening, respect the NO_DELAY flag
+            if (open_flags.is_all_set(OpenFlags.NO_DELAY)) {
+                cancel_remote_open_timer();
+                wait_for_open_async.begin();
+            }
+            
+            debug("Not opening %s: already open (open_count=%d)", to_string(), open_count);
+            
+            return false;
+        }
+        
+        this.open_flags = open_flags;
+        
+        open_internal(open_flags, cancellable);
+        
+        return true;
+    }
+    
+    private void open_internal(Folder.OpenFlags open_flags, Cancellable? cancellable) {
+        remote_semaphore = new Geary.Nonblocking.ReportingSemaphore<bool>(false);
+        
+        // start the replay queue
+        replay_queue = new ReplayQueue(this);
+        
+        // Unless NO_DELAY is set, do NOT open the remote side here; wait for the ReplayQueue to
+        // require a remote connection or wait_for_open_async() to be called ... this allows for
+        // fast local-only operations to occur, local-only either because (a) the folder has all
+        // the information required (for a list or fetch operation), or (b) the operation was de
+        // facto local-only.  In particular, EmailStore will open and close lots of folders,
+        // causing a lot of connection setup and teardown
+        //
+        // However, want to eventually open, otherwise if there's no user interaction (i.e. a
+        // second account Inbox they don't manipulate), no remote connection will ever be made,
+        // meaning that folder normalization never happens and unsolicited notifications never
+        // arrive
+        if (open_flags.is_all_set(OpenFlags.NO_DELAY))
+            wait_for_open_async.begin();
+        else
+            start_remote_open_timer();
+    }
+    
+    private void start_remote_open_timer() {
+        if (open_remote_timer_id != 0)
+            Source.remove(open_remote_timer_id);
+        
+        open_remote_timer_id = Timeout.add_seconds(FORCE_OPEN_REMOTE_TIMEOUT_SEC, on_open_remote_timeout);
+    }
+    
+    private void cancel_remote_open_timer() {
+        if (open_remote_timer_id == 0)
+            return;
+        
+        Source.remove(open_remote_timer_id);
+        open_remote_timer_id = 0;
+    }
+    
+    private bool on_open_remote_timeout() {
+        open_remote_timer_id = 0;
+        
+        // remote was not forced open due to caller, so open now
+        wait_for_open_async.begin();
+        
+        return false;
+    }
+    
+    private async void open_remote_async(Geary.Folder.OpenFlags open_flags, Cancellable? cancellable) {
+        cancel_remote_open_timer();
+        
+        // watch for folder closing before this call got a chance to execute
+        if (open_count == 0)
+            return;
+        
+        try {
+            debug("Fetching information for remote folder %s", to_string());
+            Imap.Folder folder = yield remote.fetch_folder_async(local_folder.get_path(),
+                cancellable);
+            
+            debug("Opening remote folder %s", folder.to_string());
+            yield folder.open_async(cancellable);
+            
+            // allow subclasses to examine the opened folder and resolve any vital
+            // inconsistencies
+            if (yield normalize_folders(folder, open_flags, cancellable)) {
+                // update flags, properties, etc.
+                yield local.update_folder_select_examine_async(folder, cancellable);
+                
+                // signals
+                folder.appended.connect(on_remote_appended);
+                folder.removed.connect(on_remote_removed);
+                folder.disconnected.connect(on_remote_disconnected);
+            
+                // state
+                remote_count = folder.properties.email_total;
+                
+                // all set; bless the remote folder as opened
+                remote_folder = folder;
+            } else {
+                debug("Unable to prepare remote folder %s: normalize_folders() failed", to_string());
+                notify_open_failed(Geary.Folder.OpenFailed.REMOTE_FAILED, null);
+                
+                // schedule immediate close
+                close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR, cancellable);
+                
+                return;
+            }
+        } catch (Error open_err) {
+            debug("Unable to open or prepare remote folder %s: %s", to_string(), open_err.message);
+            notify_open_failed(Geary.Folder.OpenFailed.REMOTE_FAILED, open_err);
+            
+            // schedule immediate close
+            close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR, cancellable);
+            
+            return;
+        }
+        
+        int count;
+        try {
+            count = (remote_folder != null)
+                ? remote_count
+                : yield local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE, cancellable);
+        } catch (Error count_err) {
+            debug("Unable to fetch count from local folder: %s", count_err.message);
+            
+            count = 0;
+        }
+        
+        // notify any threads of execution waiting for the remote folder to open that the result
+        // of that operation is ready
+        try {
+            remote_semaphore.notify_result(remote_folder != null, null);
+        } catch (Error notify_err) {
+            debug("Unable to fire semaphore notifying remote folder ready/not ready: %s",
+                notify_err.message);
+            
+            // do this now rather than wait for close_internal_async() to execute to ensure that
+            // any replay operations already queued don't attempt to run
+            clear_remote_folder();
+            
+            notify_open_failed(Geary.Folder.OpenFailed.REMOTE_FAILED, notify_err);
+            
+            // schedule immediate close
+            close_internal_async.begin(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_ERROR, cancellable);
+            
+            return;
+        }
+        
+        _properties.add(remote_folder.properties);
+        
+        // notify any subscribers with similar information
+        notify_opened(
+            (remote_folder != null) ? Geary.Folder.OpenState.BOTH : Geary.Folder.OpenState.LOCAL,
+            count);
+    }
+    
+    public override async void close_async(Cancellable? cancellable = null) throws Error {
+        if (open_count == 0 || --open_count > 0)
+            return;
+        
+        if (remote_folder != null)
+            _properties.remove(remote_folder.properties);
+        
+        yield close_internal_async(CloseReason.LOCAL_CLOSE, CloseReason.REMOTE_CLOSE, cancellable);
+    }
+    
+    // NOTE: This bypasses open_count and forces the Folder closed.
+    internal async void close_internal_async(Folder.CloseReason local_reason, Folder.CloseReason 
remote_reason,
+        Cancellable? cancellable) {
+        cancel_remote_open_timer();
+        
+        // only flushing pending ReplayOperations if this is a "clean" close, not forced due to
+        // error
+        bool flush_pending = !remote_reason.is_error();
+        
+        // If closing due to error, notify all operations waiting for the remote that it's not
+        // coming available ... this wakes up any ReplayOperation blocking on wait_for_open_async(),
+        // necessary in order to finish ReplayQueue.close_async (i.e. to prevent deadlock); this
+        // is necessary because it's possible for this method to be called before the remote_folder
+        // has even had a chance to open.
+        //
+        // Note that we don't want to do this for a clean close, because we want to flush out
+        // pending operations first
+        Imap.Folder? closing_remote_folder = null;
+        if (!flush_pending)
+            closing_remote_folder = clear_remote_folder();
+        
+        // Close the replay queues; if a "clean" close, flush pending operations so everything
+        // gets a chance to run; if forced close, drop everything outstanding
+        try {
+            if (replay_queue != null) {
+                debug("Closing replay queue for %s... (flush_pending=%s)", to_string(),
+                    flush_pending.to_string());
+                yield replay_queue.close_async(flush_pending);
+                debug("Closed replay queue for %s", to_string());
+            }
+        } catch (Error replay_queue_err) {
+            debug("Error closing %s replay queue: %s", to_string(), replay_queue_err.message);
+        }
+        
+        replay_queue = null;
+        
+        // if a "clean" close, now go ahead and close the folder
+        if (flush_pending)
+            closing_remote_folder = clear_remote_folder();
+        
+        if (closing_remote_folder != null) {
+            // to avoid keeping the caller waiting while the remote end closes (i.e. drops the
+            // connection or performs an IMAP CLOSE operation), close it in the background and
+            // reestablish connection there, if necessary
+            //
+            // TODO: Problem with this is that we cannot effectively signal or report a close error,
+            // because by the time this operation completes the folder is considered closed.  That
+            // may not be important to most callers, however.
+            //
+            // It also means the reference to the Folder must be maintained until completely
+            // closed.  Also not a problem, as GenericAccount does that internally.  However, this
+            // might be an issue if GenericAccount removes this folder due to a user command or
+            // detection on the server, so this background op keeps a reference to the Folder
+            close_remote_folder_async.begin(this, closing_remote_folder, remote_reason);
+        }
+        
+        remote_opened = false;
+        
+        // if remote reason is an error, then close_remote_folder_async() will be performing
+        // reestablishment, so go no further
+        if (remote_reason.is_error())
+            return;
+        
+        // forced closed one way or another
+        open_count = 0;
+        
+        // use remote_reason even if remote_folder was null; it could be that the error occurred
+        // while opening and remote_folder was yet unassigned ... also, need to call this every
+        // time, even if remote was not fully opened, as some callers rely on order of signals
+        notify_closed(remote_reason);
+        
+        // see above note for why this must be called every time
+        notify_closed(local_reason);
+        
+        notify_closed(CloseReason.FOLDER_CLOSED);
+        
+        debug("Folder %s closed", to_string());
+    }
+    
+    // Returns the remote_folder, if it was set
+    private Imap.Folder? clear_remote_folder() {
+        if (remote_folder != null) {
+            // disconnect signals before ripping out reference
+            remote_folder.appended.disconnect(on_remote_appended);
+            remote_folder.removed.disconnect(on_remote_removed);
+            remote_folder.disconnected.disconnect(on_remote_disconnected);
+        }
+        
+        Imap.Folder? old_remote_folder = remote_folder;
+        remote_folder = null;
+        remote_count = -1;
+        
+        remote_semaphore.reset();
+        try {
+            remote_semaphore.notify_result(false, null);
+        } catch (Error err) {
+            debug("Error attempting to notify that remote folder %s is now closed: %s", to_string(),
+                err.message);
+        }
+        
+        return old_remote_folder;
+    }
+    
+    // See note in close_async() for why this method is static and uses an owned ref
+    private static async void close_remote_folder_async(owned MinimalFolder folder,
+        owned Imap.Folder remote_folder, Folder.CloseReason remote_reason) {
+        // force the remote closed; if due to a remote disconnect and plan on reopening, *still*
+        // need to do this
+        try {
+            yield remote_folder.close_async(null);
+        } catch (Error err) {
+            debug("Unable to close remote %s: %s", remote_folder.to_string(), err.message);
+            
+            // fallthrough
+        }
+        
+        // reestablish connection (which requires renormalizing the remote with the local) if
+        // close was in error
+        if (remote_reason.is_error()) {
+            debug("Reestablishing broken connection to %s", folder.to_string());
+            folder.open_internal(OpenFlags.NO_DELAY, null);
+        }
+    }
+    
+    public override async void find_boundaries_async(Gee.Collection<Geary.EmailIdentifier> ids,
+        out Geary.EmailIdentifier? low, out Geary.EmailIdentifier? high,
+        Cancellable? cancellable = null) throws Error {
+        low = null;
+        high = null;
+        
+        Gee.MultiMap<Geary.EmailIdentifier, Geary.FolderPath>? map
+            = yield account.get_containing_folders_async(ids, cancellable);
+        
+        if (map != null) {
+            Gee.ArrayList<Geary.EmailIdentifier> in_folder = new Gee.ArrayList<Geary.EmailIdentifier>();
+            foreach (Geary.EmailIdentifier id in map.get_keys()) {
+                if (path in map.get(id))
+                    in_folder.add(id);
+            }
+            
+            if (in_folder.size > 0) {
+                Gee.SortedSet<Geary.EmailIdentifier> sorted = Geary.EmailIdentifier.sort(in_folder);
+                
+                low = sorted.first();
+                high = sorted.last();
+            }
+        }
+    }
+    
+    private void on_email_complete(Gee.Collection<Geary.EmailIdentifier> email_ids) {
+        notify_email_locally_complete(email_ids);
+    }
+    
+    private void on_remote_appended(int reported_remote_count) {
+        debug("%s on_remote_appended: remote_count=%d reported_remote_count=%d", to_string(), remote_count,
+            reported_remote_count);
+        
+        if (reported_remote_count < 0)
+            return;
+        
+        // from the new remote total and the old remote total, glean the SequenceNumbers of the
+        // new email(s)
+        Gee.List<Imap.SequenceNumber> positions = new Gee.ArrayList<Imap.SequenceNumber>();
+        for (int pos = remote_count + 1; pos <= reported_remote_count; pos++)
+            positions.add(new Imap.SequenceNumber(pos));
+        
+        // store the remote count NOW, as further appended messages could arrive before the
+        // ReplayAppend executes
+        remote_count = reported_remote_count;
+        
+        if (positions.size > 0)
+            replay_queue.schedule_server_notification(new ReplayAppend(this, reported_remote_count, 
positions));
+    }
+    
+    // Need to prefetch at least an EmailIdentifier (and duplicate detection fields) to create a
+    // normalized placeholder in the local database of the message, so all positions are
+    // properly relative to the end of the message list; once this is done, notify user of new
+    // messages.  If duplicates, create_email_async() will fall through to an updated merge,
+    // which is exactly what we want.
+    //
+    // This MUST only be called from ReplayAppend.
+    internal async void do_replay_appended_messages(int reported_remote_count,
+        Gee.List<Imap.SequenceNumber> remote_positions) {
+        StringBuilder positions_builder = new StringBuilder("( ");
+        foreach (Imap.SequenceNumber remote_position in remote_positions)
+            positions_builder.append_printf("%s ", remote_position.to_string());
+        positions_builder.append(")");
+        
+        debug("%s do_replay_appended_message: current remote_count=%d reported_remote_count=%d 
remote_positions=%s",
+            to_string(), remote_count, reported_remote_count, positions_builder.str);
+        
+        if (remote_positions.size == 0)
+            return;
+        
+        Gee.HashSet<Geary.EmailIdentifier> created = new Gee.HashSet<Geary.EmailIdentifier>();
+        Gee.HashSet<Geary.EmailIdentifier> appended = new Gee.HashSet<Geary.EmailIdentifier>();
+        try {
+            // If remote doesn't fully open, then don't fire signal, as we'll be unable to
+            // normalize the folder
+            if (!yield remote_semaphore.wait_for_result_async(null)) {
+                debug("%s do_replay_appended_message: remote never opened", to_string());
+                
+                return;
+            }
+            
+            Imap.MessageSet msg_set = new Imap.MessageSet.sparse(remote_positions.to_array());
+            Gee.List<Geary.Email>? list = yield remote_folder.list_email_async(msg_set,
+                ImapDB.Folder.REQUIRED_FIELDS, null);
+            if (list != null && list.size > 0) {
+                debug("%s do_replay_appended_message: %d new messages in %s", to_string(),
+                    list.size, msg_set.to_string());
+                
+                // need to report both if it was created (not known before) and appended (which
+                // could mean created or simply a known email associated with this folder)
+                Gee.Map<Geary.Email, bool> created_or_merged =
+                    yield local_folder.create_or_merge_email_async(list, null);
+                foreach (Geary.Email email in created_or_merged.keys) {
+                    // true means created
+                    if (created_or_merged.get(email)) {
+                        debug("%s do_replay_appended_message: appended email ID %s added",
+                            to_string(), email.id.to_string());
+                        
+                        created.add(email.id);
+                    } else {
+                        debug("%s do_replay_appended_message: appended email ID %s associated",
+                            to_string(), email.id.to_string());
+                    }
+                    
+                    appended.add(email.id);
+                }
+            } else {
+                debug("%s do_replay_appended_message: no new messages in %s", to_string(),
+                    msg_set.to_string());
+            }
+        } catch (Error err) {
+            debug("%s do_replay_appended_message: Unable to process: %s",
+                to_string(), err.message);
+        }
+        
+        // store the reported count, *not* the current count (which is updated outside the of
+        // the queue) to ensure that updates happen serially and reflect committed local changes
+        try {
+            yield local_folder.update_remote_selected_message_count(reported_remote_count, null);
+        } catch (Error err) {
+            debug("%s do_replay_appended_message: Unable to save appended remote count %d: %s",
+                to_string(), reported_remote_count, err.message);
+        }
+        
+        if (appended.size > 0)
+            notify_email_appended(appended);
+        
+        if (created.size > 0)
+            notify_email_locally_appended(created);
+        
+        notify_email_count_changed(reported_remote_count, CountChangeReason.APPENDED);
+        
+        debug("%s do_replay_appended_message: completed, current remote_count=%d reported_remote_count=%d",
+            to_string(), remote_count, reported_remote_count);
+    }
+    
+    private void on_remote_removed(Imap.SequenceNumber position, int reported_remote_count) {
+        debug("%s on_remote_removed: remote_count=%d position=%s reported_remote_count=%d", to_string(),
+            remote_count, position.to_string(), reported_remote_count);
+        
+        if (reported_remote_count < 0)
+            return;
+        
+        // notify of removal to all pending replay operations
+        replay_queue.notify_remote_removed_position(position);
+        
+        // update remote count NOW, as further appended and removed messages can arrive before
+        // ReplayRemoval executes
+        //
+        // something to note at this point: the ExpungeEmail operation marks messages as removed,
+        // then signals they're removed and reports an adjusted count in its replay_local_async().
+        // remote_count is *not* updated, which is why it's safe to do that here without worry.
+        // similarly, signals are only fired here if marked, so the same EmailIdentifier isn't
+        // reported twice
+        remote_count = reported_remote_count;
+        
+        replay_queue.schedule_server_notification(new ReplayRemoval(this, reported_remote_count, position));
+    }
+    
+    // This MUST only be called from ReplayRemoval.
+    internal async void do_replay_removed_message(int reported_remote_count, Imap.SequenceNumber 
remote_position) {
+        debug("%s do_replay_removed_message: current remote_count=%d remote_position=%d 
reported_remote_count=%d",
+            to_string(), remote_count, remote_position.value, reported_remote_count);
+        
+        if (!remote_position.is_valid()) {
+            debug("%s do_replay_removed_message: ignoring, invalid remote position or count",
+                to_string());
+            
+            return;
+        }
+        
+        int local_count = -1;
+        int local_position = -1;
+        
+        ImapDB.EmailIdentifier? owned_id = null;
+        try {
+            // need total count, including those marked for removal, to accurately calculate position
+            // from server's point of view, not client's
+            local_count = yield local_folder.get_email_count_async(
+                ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null);
+            local_position = remote_position.value - (reported_remote_count + 1 - local_count);
+            
+            // zero or negative means the message exists beyond the local vector's range, so
+            // nothing to do there
+            if (local_position > 0) {
+                debug("%s do_replay_removed_message: local_count=%d local_position=%d", to_string(),
+                    local_count, local_position);
+                
+                owned_id = yield local_folder.get_id_at_async(local_position, null);
+            } else {
+                debug("%s do_replay_removed_message: message not stored locally (local_count=%d 
local_position=%d)",
+                    to_string(), local_count, local_position);
+            }
+        } catch (Error err) {
+            debug("%s do_replay_removed_message: unable to determine ID of removed message %s: %s",
+                to_string(), remote_position.to_string(), err.message);
+        }
+        
+        bool marked = false;
+        if (owned_id != null) {
+            debug("%s do_replay_removed_message: detaching from local store Email ID %s", to_string(),
+                owned_id.to_string());
+            try {
+                // Reflect change in the local store and notify subscribers
+                yield local_folder.detach_single_email_async(owned_id, out marked, null);
+            } catch (Error err) {
+                debug("%s do_replay_removed_message: unable to remove message #%s: %s", to_string(),
+                    remote_position.to_string(), err.message);
+            }
+            
+            // Notify queued replay operations that the email has been removed (by EmailIdentifier)
+            replay_queue.notify_remote_removed_ids(
+                Geary.iterate<ImapDB.EmailIdentifier>(owned_id).to_array_list());
+        } else {
+            debug("%s do_replay_removed_message: remote_position=%d unknown in local store "
+                + "(reported_remote_count=%d local_position=%d local_count=%d)",
+                to_string(), remote_position.value, reported_remote_count, local_position, local_count);
+        }
+        
+        // for debugging
+        int new_local_count = -1;
+        try {
+            new_local_count = yield local_folder.get_email_count_async(
+                ImapDB.Folder.ListFlags.INCLUDE_MARKED_FOR_REMOVE, null);
+        } catch (Error err) {
+            debug("%s do_replay_removed_message: error fetching new local count: %s", to_string(),
+                err.message);
+        }
+        
+        // as with on_remote_appended(), only update in local store inside a queue operation, to
+        // ensure serial commits
+        try {
+            yield local_folder.update_remote_selected_message_count(reported_remote_count, null);
+        } catch (Error err) {
+            debug("%s do_replay_removed_message: unable to save removed remote count: %s", to_string(),
+                err.message);
+        }
+        
+        // notify of change
+        if (!marked && owned_id != null)
+            notify_email_removed(Geary.iterate<Geary.EmailIdentifier>(owned_id).to_array_list());
+        
+        if (!marked)
+            notify_email_count_changed(reported_remote_count, CountChangeReason.REMOVED);
+        
+        debug("%s do_replay_remove_message: completed, current remote_count=%d "
+            + "(reported_remote_count=%d local_count=%d starting local_count=%d remote_position=%d 
local_position=%d marked=%s)",
+            to_string(), remote_count, reported_remote_count, new_local_count, local_count, 
remote_position.value,
+            local_position, marked.to_string());
+    }
+    
+    private void on_remote_disconnected(Imap.ClientSession.DisconnectReason reason) {
+        debug("on_remote_disconnected: reason=%s", reason.to_string());
+        
+        replay_queue.schedule(new ReplayDisconnect(this, reason));
+    }
+    
+    //
+    // list_email_by_id variants
+    //
+    
+    public override async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier? initial_id,
+        int count, Geary.Email.Field required_fields, Folder.ListFlags flags,
+        Cancellable? cancellable = null) throws Error {
+        Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
+        yield do_list_email_by_id_async("list_email_by_id_async", initial_id, count, required_fields,
+            flags, accumulator, null, cancellable);
+        
+        return !accumulator.is_empty ? accumulator : null;
+    }
+    
+    public override void lazy_list_email_by_id(Geary.EmailIdentifier? initial_id, int count,
+        Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb,
+        Cancellable? cancellable = null) {
+        do_lazy_list_email_by_id_async.begin(initial_id, count, required_fields, flags, cb, cancellable);
+    }
+    
+    private async void do_lazy_list_email_by_id_async(Geary.EmailIdentifier? initial_id, int count,
+        Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? 
cancellable) {
+        try {
+            yield do_list_email_by_id_async("lazy_list_email_by_id", initial_id, count, required_fields,
+                flags, null, cb, cancellable);
+        } catch (Error err) {
+            cb(null, err);
+        }
+    }
+    
+    private async void do_list_email_by_id_async(string method, Geary.EmailIdentifier? initial_id,
+        int count, Geary.Email.Field required_fields, Folder.ListFlags flags,
+        Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable) throws Error {
+        check_open(method);
+        check_flags(method, flags);
+        if (initial_id != null)
+            check_id(method, initial_id);
+        
+        if (count == 0) {
+            // signal finished
+            if (cb != null)
+                cb(null, null);
+            
+            return;
+        }
+        
+        // Schedule list operation and wait for completion.
+        ListEmailByID op = new ListEmailByID(this, (ImapDB.EmailIdentifier) initial_id, count,
+            required_fields, flags, accumulator, cb, cancellable);
+        replay_queue.schedule(op);
+        
+        yield op.wait_for_ready_async(cancellable);
+    }
+    
+    //
+    // list_email_by_sparse_id variants
+    //
+    
+    public async override Gee.List<Geary.Email>? list_email_by_sparse_id_async(
+        Gee.Collection<Geary.EmailIdentifier> ids, Geary.Email.Field required_fields, Folder.ListFlags flags,
+        Cancellable? cancellable = null) throws Error {
+        Gee.ArrayList<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
+        yield do_list_email_by_sparse_id_async("list_email_by_sparse_id_async", ids, required_fields,
+            flags, accumulator, null, cancellable);
+        
+        return (accumulator.size > 0) ? accumulator : null;
+    }
+    
+    public override void lazy_list_email_by_sparse_id(Gee.Collection<Geary.EmailIdentifier> ids,
+        Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? 
cancellable = null) {
+        do_lazy_list_email_by_sparse_id_async.begin(ids, required_fields, flags, cb, cancellable);
+    }
+    
+    private async void do_lazy_list_email_by_sparse_id_async(Gee.Collection<Geary.EmailIdentifier> ids,
+        Geary.Email.Field required_fields, Folder.ListFlags flags, EmailCallback cb, Cancellable? 
cancellable) {
+        try {
+            yield do_list_email_by_sparse_id_async("lazy_list_email_by_sparse_id", ids, required_fields,
+                flags, null, cb, cancellable);
+        } catch (Error err) {
+            cb(null, err);
+        }
+    }
+    
+    private async void do_list_email_by_sparse_id_async(string method,
+        Gee.Collection<Geary.EmailIdentifier> ids, Geary.Email.Field required_fields, Folder.ListFlags flags,
+        Gee.List<Geary.Email>? accumulator, EmailCallback? cb, Cancellable? cancellable = null) throws Error 
{
+        check_open(method);
+        check_flags(method, flags);
+        check_ids(method, ids);
+        
+        if (ids.size == 0) {
+            // signal finished
+            if (cb != null)
+                cb(null, null);
+            
+            return;
+        }
+        
+        // Schedule list operation and wait for completion.
+        // TODO: Break up requests to avoid hogging the queue
+        ListEmailBySparseID op = new ListEmailBySparseID(this, (Gee.Collection<ImapDB.EmailIdentifier>) ids,
+            required_fields, flags, accumulator, cb, cancellable);
+        replay_queue.schedule(op);
+        
+        yield op.wait_for_ready_async(cancellable);
+    }
+    
+    public override async Gee.Map<Geary.EmailIdentifier, Geary.Email.Field>? list_local_email_fields_async(
+        Gee.Collection<Geary.EmailIdentifier> ids, Cancellable? cancellable = null) throws Error {
+        check_open("list_local_email_fields_async");
+        check_ids("list_local_email_fields_async", ids);
+        
+        return yield local_folder.list_email_fields_by_id_async(
+            (Gee.Collection<Geary.ImapDB.EmailIdentifier>) ids, ImapDB.Folder.ListFlags.NONE, cancellable);
+    }
+    
+    public override async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
+        Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable = null)
+        throws Error {
+        check_open("fetch_email_async");
+        check_flags("fetch_email_async", flags);
+        check_id("fetch_email_async", id);
+        
+        FetchEmail op = new FetchEmail(this, (ImapDB.EmailIdentifier) id, required_fields, flags,
+            cancellable);
+        replay_queue.schedule(op);
+        
+        yield op.wait_for_ready_async(cancellable);
+        
+        if (op.email == null) {
+            throw new EngineError.NOT_FOUND("Email %s not found in %s", id.to_string(), to_string());
+        } else if (!op.email.fields.fulfills(required_fields)) {
+            throw new EngineError.INCOMPLETE_MESSAGE("Email %s in %s does not fulfill required fields %Xh 
(has %Xh)",
+                id.to_string(), to_string(), required_fields, op.email.fields);
+        }
+        
+        return op.email;
+    }
+    
+    // Helper function for child classes dealing with the delete/archive question.  This method will
+    // mark the message as deleted and expunge it.
+    protected async void expunge_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
+        Cancellable? cancellable = null) throws Error {
+        check_open("expunge_email_async");
+        check_ids("expunge_email_async", email_ids);
+        
+        RemoveEmail remove = new RemoveEmail(this, (Gee.List<ImapDB.EmailIdentifier>) email_ids,
+            cancellable);
+        replay_queue.schedule(remove);
+        
+        yield remove.wait_for_ready_async(cancellable);
+    }
+    
+    private void check_open(string method) throws EngineError {
+        if (open_count == 0)
+            throw new EngineError.OPEN_REQUIRED("%s failed: folder %s is not open", method, to_string());
+    }
+    
+    private void check_flags(string method, Folder.ListFlags flags) throws EngineError {
+        if (flags.is_all_set(Folder.ListFlags.LOCAL_ONLY) && 
flags.is_all_set(Folder.ListFlags.FORCE_UPDATE)) {
+            throw new EngineError.BAD_PARAMETERS("%s %s failed: LOCAL_ONLY and FORCE_UPDATE are mutually 
exclusive",
+                to_string(), method);
+        }
+    }
+    
+    private void check_id(string method, EmailIdentifier id) throws EngineError {
+        if (!(id is ImapDB.EmailIdentifier))
+            throw new EngineError.BAD_PARAMETERS("Email ID %s is not IMAP Email ID", id.to_string());
+    }
+    
+    private void check_ids(string method, Gee.Collection<EmailIdentifier> ids) throws EngineError {
+        foreach (EmailIdentifier id in ids)
+            check_id(method, id);
+    }
+    
+    public virtual async void mark_email_async(Gee.List<Geary.EmailIdentifier> to_mark,
+        Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, 
+        Cancellable? cancellable = null) throws Error {
+        check_open("mark_email_async");
+        
+        MarkEmail mark = new MarkEmail(this, to_mark, flags_to_add, flags_to_remove, cancellable);
+        replay_queue.schedule(mark);
+        yield mark.wait_for_ready_async(cancellable);
+    }
+
+    public virtual async void copy_email_async(Gee.List<Geary.EmailIdentifier> to_copy,
+        Geary.FolderPath destination, Cancellable? cancellable = null) throws Error {
+        check_open("copy_email_async");
+        check_ids("copy_email_async", to_copy);
+        
+        CopyEmail copy = new CopyEmail(this, (Gee.List<ImapDB.EmailIdentifier>) to_copy, destination);
+        replay_queue.schedule(copy);
+        yield copy.wait_for_ready_async(cancellable);
+    }
+
+    public virtual async void move_email_async(Gee.List<Geary.EmailIdentifier> to_move,
+        Geary.FolderPath destination, Cancellable? cancellable = null) throws Error {
+        check_open("move_email_async");
+        check_ids("move_email_async", to_move);
+        
+        MoveEmail move = new MoveEmail(this, (Gee.List<ImapDB.EmailIdentifier>) to_move, destination);
+        replay_queue.schedule(move);
+        yield move.wait_for_ready_async(cancellable);
+    }
+    
+    private void on_email_flags_changed(Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> changed) {
+        notify_email_flags_changed(changed);
+    }
+    
+    // TODO: A proper public search mechanism; note that this always round-trips to the remote,
+    // doesn't go through the replay queue, and doesn't deal with messages marked for deletion
+    internal async Geary.EmailIdentifier? find_earliest_email_async(DateTime datetime,
+        Geary.EmailIdentifier? before_id, Cancellable? cancellable) throws Error {
+        check_open("find_earliest_email_async");
+        if (before_id != null)
+            check_id("find_earliest_email_async", before_id);
+        
+        Imap.SearchCriteria criteria = new Imap.SearchCriteria();
+        criteria.is_(Imap.SearchCriterion.since_internaldate(new 
Imap.InternalDate.from_date_time(datetime)));
+        
+        // if before_id available, only search for messages before it
+        if (before_id != null) {
+            Imap.UID? before_uid = yield local_folder.get_uid_async((ImapDB.EmailIdentifier) before_id,
+                ImapDB.Folder.ListFlags.NONE, cancellable);
+            if (before_uid == null) {
+                throw new EngineError.NOT_FOUND("before_id %s not found in %s", before_id.to_string(),
+                    to_string());
+            }
+            
+            criteria.and(Imap.SearchCriterion.message_set(
+                new Imap.MessageSet.uid_range(new Imap.UID(Imap.UID.MIN), before_uid.previous(true))));
+        }
+        
+        Gee.List<Geary.Email> accumulator = new Gee.ArrayList<Geary.Email>();
+        ServerSearchEmail op = new ServerSearchEmail(this, criteria, Geary.Email.Field.NONE,
+            accumulator, cancellable);
+        
+        // need to check again due to the yield in the above conditional block
+        check_open("find_earliest_email_async.schedule operation");
+        
+        replay_queue.schedule(op);
+        
+        if (!yield op.wait_for_ready_async(cancellable))
+            return null;
+        
+        // find earliest ID; because all Email comes from Folder, UID should always be present
+        ImapDB.EmailIdentifier? earliest_id = null;
+        foreach (Geary.Email email in accumulator) {
+            ImapDB.EmailIdentifier email_id = (ImapDB.EmailIdentifier) email.id;
+            
+            if (earliest_id == null || email_id.uid.compare_to(earliest_id.uid) < 0)
+                earliest_id = email_id;
+        }
+        
+        return earliest_id;
+    }
+    
+    public virtual async Geary.EmailIdentifier? create_email_async(
+        RFC822.Message rfc822, Geary.EmailFlags? flags, DateTime? date_received,
+        Geary.EmailIdentifier? id, Cancellable? cancellable = null) throws Error {
+        check_open("create_email_async");
+        if (id != null)
+            check_id("create_email_async", id);
+        
+        Error? cancel_error = null;
+        Geary.EmailIdentifier? ret = null;
+        try {
+            CreateEmail create = new CreateEmail(this, rfc822, flags, date_received, cancellable);
+            replay_queue.schedule(create);
+            yield create.wait_for_ready_async(cancellable);
+            
+            ret = create.created_id;
+        } catch (Error e) {
+            if (e is IOError.CANCELLED)
+                cancel_error = e;
+            else
+                throw e;
+        }
+        
+        Geary.FolderSupport.Remove? remove_folder = this as Geary.FolderSupport.Remove;
+        
+        // Remove old message.
+        if (id != null && remove_folder != null)
+            yield remove_folder.remove_single_email_async(id, null);
+        
+        // If the user cancelled the operation, throw the error here.
+        if (cancel_error != null)
+            throw cancel_error;
+        
+        // If the caller cancelled during the remove operation, delete the newly created message to
+        // safely back out.
+        if (cancellable != null && cancellable.is_cancelled() && ret != null && remove_folder != null)
+            yield remove_folder.remove_single_email_async(ret, null);
+        
+        return ret;
+    }
+}
+
diff --git a/src/engine/imap-engine/imap-engine-replay-queue.vala 
b/src/engine/imap-engine/imap-engine-replay-queue.vala
index 5c43311..26ba93a 100644
--- a/src/engine/imap-engine/imap-engine-replay-queue.vala
+++ b/src/engine/imap-engine/imap-engine-replay-queue.vala
@@ -49,7 +49,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
         return remote_queue.size;
     } }
     
-    private weak GenericFolder owner;
+    private weak MinimalFolder owner;
     private Nonblocking.Mailbox<ReplayOperation> local_queue = new Nonblocking.Mailbox<ReplayOperation>();
     private Nonblocking.Mailbox<ReplayOperation> remote_queue = new Nonblocking.Mailbox<ReplayOperation>();
     private ReplayOperation? local_op_active = null;
@@ -124,7 +124,7 @@ private class Geary.ImapEngine.ReplayQueue : Geary.BaseObject {
      * each ReplayOperation waiting to perform a remote operation, cancelling it if the remote
      * folder is not ready.
      */
-    public ReplayQueue(GenericFolder owner) {
+    public ReplayQueue(MinimalFolder owner) {
         this.owner = owner;
         
         // fire off background queue processors
diff --git a/src/engine/imap-engine/other/imap-engine-other-account.vala 
b/src/engine/imap-engine/other/imap-engine-other-account.vala
index 419a432..2d2f5b5 100644
--- a/src/engine/imap-engine/other/imap-engine-other-account.vala
+++ b/src/engine/imap-engine/other/imap-engine-other-account.vala
@@ -10,7 +10,7 @@ private class Geary.ImapEngine.OtherAccount : Geary.ImapEngine.GenericAccount {
         base (name, account_information, false, remote, local);
     }
     
-    protected override GenericFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
+    protected override MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
         ImapDB.Account local_account, ImapDB.Folder local_folder) {
         SpecialFolderType type;
         if (Imap.MailboxSpecifier.folder_path_is_inbox(path))
@@ -18,22 +18,7 @@ private class Geary.ImapEngine.OtherAccount : Geary.ImapEngine.GenericAccount {
         else
             type = local_folder.get_properties().attrs.get_special_folder_type();
         
-        switch (type) {
-            case SpecialFolderType.SENT:
-                return new GenericSentMailFolder(this, remote_account, local_account, local_folder,
-                    type);
-            
-            case SpecialFolderType.TRASH:
-                return new GenericTrashFolder(this, remote_account, local_account, local_folder,
-                    type);
-            
-            case SpecialFolderType.DRAFTS:
-                return new GenericDraftsFolder(this, remote_account, local_account, local_folder,
-                    type);
-            
-            default:
-                return new OtherFolder(this, remote_account, local_account, local_folder, type);
-        }
+        return new OtherFolder(this, remote_account, local_account, local_folder, type);
     }
 }
 
diff --git a/src/engine/imap-engine/other/imap-engine-other-folder.vala 
b/src/engine/imap-engine/other/imap-engine-other-folder.vala
index fe8e973..7ba76ae 100644
--- a/src/engine/imap-engine/other/imap-engine-other-folder.vala
+++ b/src/engine/imap-engine/other/imap-engine-other-folder.vala
@@ -4,15 +4,10 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-private class Geary.ImapEngine.OtherFolder : GenericFolder, Geary.FolderSupport.Remove {
+private class Geary.ImapEngine.OtherFolder : GenericFolder {
     public OtherFolder(OtherAccount account, Imap.Account remote, ImapDB.Account local,
         ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
         base (account, remote, local, local_folder, special_folder_type);
     }
-    
-    public async void remove_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
-        Cancellable? cancellable = null) throws Error {
-        yield expunge_email_async(email_ids, cancellable);
-    }
 }
 
diff --git a/src/engine/imap-engine/outlook/imap-engine-outlook-account.vala 
b/src/engine/imap-engine/outlook/imap-engine-outlook-account.vala
index 4007f10..2f0e779 100644
--- a/src/engine/imap-engine/outlook/imap-engine-outlook-account.vala
+++ b/src/engine/imap-engine/outlook/imap-engine-outlook-account.vala
@@ -36,7 +36,7 @@ private class Geary.ImapEngine.OutlookAccount : Geary.ImapEngine.GenericAccount
         base (name, account_information, false, remote, local);
     }
     
-    protected override GenericFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
+    protected override MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
         ImapDB.Account local_account, ImapDB.Folder local_folder) {
         // use the Folder's attributes to determine if it's a special folder type, unless it's
         // INBOX; that's determined by name
@@ -46,23 +46,8 @@ private class Geary.ImapEngine.OutlookAccount : Geary.ImapEngine.GenericAccount
         else
             special_folder_type = local_folder.get_properties().attrs.get_special_folder_type();
         
-        // generate properly-interfaced Folder depending on the special type
-        // Proper Drafts support depends on Outlook.com supporting UIDPLUS or us devising another
-        // mechanism to associate new messages with drafts-in-progress; see
-        // http://redmine.yorba.org/issues/7495
-        switch (special_folder_type) {
-            case SpecialFolderType.SENT:
-                return new GenericSentMailFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-            
-            case SpecialFolderType.TRASH:
-                return new GenericTrashFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-            
-            default:
-                return new OutlookFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-        }
+        return new OutlookFolder(this, remote_account, local_account, local_folder,
+            special_folder_type);
     }
 }
 
diff --git a/src/engine/imap-engine/outlook/imap-engine-outlook-folder.vala 
b/src/engine/imap-engine/outlook/imap-engine-outlook-folder.vala
index c787687..78bb4d1 100644
--- a/src/engine/imap-engine/outlook/imap-engine-outlook-folder.vala
+++ b/src/engine/imap-engine/outlook/imap-engine-outlook-folder.vala
@@ -4,15 +4,10 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-private class Geary.ImapEngine.OutlookFolder : GenericFolder, Geary.FolderSupport.Remove {
+private class Geary.ImapEngine.OutlookFolder : GenericFolder {
     public OutlookFolder(OutlookAccount account, Imap.Account remote, ImapDB.Account local,
         ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
         base (account, remote, local, local_folder, special_folder_type);
     }
-    
-    public async void remove_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
-        Cancellable? cancellable = null) throws Error {
-        yield expunge_email_async(email_ids, cancellable);
-    }
 }
 
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
index 7d17d0c..b513cc9 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-abstract-list-email.vala
@@ -7,7 +7,7 @@
 private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.SendReplayOperation {
     private class RemoteBatchOperation : Nonblocking.BatchOperation {
         // IN
-        public GenericFolder owner;
+        public MinimalFolder owner;
         public Imap.MessageSet msg_set;
         public Geary.Email.Field unfulfilled_fields;
         public Geary.Email.Field required_fields;
@@ -15,7 +15,7 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
         // OUT
         public Gee.Set<Geary.EmailIdentifier> created_ids = new Gee.HashSet<Geary.EmailIdentifier>();
         
-        public RemoteBatchOperation(GenericFolder owner, Imap.MessageSet msg_set,
+        public RemoteBatchOperation(MinimalFolder owner, Imap.MessageSet msg_set,
             Geary.Email.Field unfulfilled_fields, Geary.Email.Field required_fields) {
             this.owner = owner;
             this.msg_set = msg_set;
@@ -53,7 +53,7 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
         }
     }
     
-    protected GenericFolder owner;
+    protected MinimalFolder owner;
     protected Geary.Email.Field required_fields;
     protected Gee.List<Geary.Email>? accumulator = null;
     protected weak EmailCallback? cb;
@@ -62,7 +62,7 @@ private abstract class Geary.ImapEngine.AbstractListEmail : Geary.ImapEngine.Sen
     
     private Gee.HashMap<Imap.UID, Geary.Email.Field> unfulfilled = new Gee.HashMap<Imap.UID, 
Geary.Email.Field>();
     
-    public AbstractListEmail(string name, GenericFolder owner, Geary.Email.Field required_fields,
+    public AbstractListEmail(string name, MinimalFolder owner, Geary.Email.Field required_fields,
         Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator, EmailCallback? cb,
         Cancellable? cancellable) {
         base(name);
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
index 20dc640..3ab6272 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-copy-email.vala
@@ -5,12 +5,12 @@
  */
 
 private class Geary.ImapEngine.CopyEmail : Geary.ImapEngine.SendReplayOperation {
-    private GenericFolder engine;
+    private MinimalFolder engine;
     private Gee.HashSet<ImapDB.EmailIdentifier> to_copy = new Gee.HashSet<ImapDB.EmailIdentifier>();
     private Geary.FolderPath destination;
     private Cancellable? cancellable;
 
-    public CopyEmail(GenericFolder engine, Gee.List<ImapDB.EmailIdentifier> to_copy, 
+    public CopyEmail(MinimalFolder engine, Gee.List<ImapDB.EmailIdentifier> to_copy, 
         Geary.FolderPath destination, Cancellable? cancellable = null) {
         base("CopyEmail");
         
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
index 4419aa0..db33e0e 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-create-email.vala
@@ -7,13 +7,13 @@
 private class Geary.ImapEngine.CreateEmail : Geary.ImapEngine.SendReplayOperation {
     public Geary.EmailIdentifier? created_id { get; private set; default = null; }
     
-    private GenericFolder engine;
+    private MinimalFolder engine;
     private RFC822.Message rfc822;
     private Geary.EmailFlags? flags;
     private DateTime? date_received;
     private Cancellable? cancellable;
     
-    public CreateEmail(GenericFolder engine, RFC822.Message rfc822, Geary.EmailFlags? flags,
+    public CreateEmail(MinimalFolder engine, RFC822.Message rfc822, Geary.EmailFlags? flags,
         DateTime? date_received, Cancellable? cancellable) {
         base.only_remote("CreateEmail");
         
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
index 02c0970..bf43cd5 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-fetch-email.vala
@@ -7,7 +7,7 @@
 private class Geary.ImapEngine.FetchEmail : Geary.ImapEngine.SendReplayOperation {
     public Email? email = null;
     
-    private GenericFolder engine;
+    private MinimalFolder engine;
     private ImapDB.EmailIdentifier id;
     private Email.Field required_fields;
     private Email.Field remaining_fields;
@@ -16,7 +16,7 @@ private class Geary.ImapEngine.FetchEmail : Geary.ImapEngine.SendReplayOperation
     private Imap.UID? uid = null;
     private bool remote_removed = false;
     
-    public FetchEmail(GenericFolder engine, ImapDB.EmailIdentifier id, Email.Field required_fields,
+    public FetchEmail(MinimalFolder engine, ImapDB.EmailIdentifier id, Email.Field required_fields,
         Folder.ListFlags flags, Cancellable? cancellable) {
         base ("FetchEmail");
         
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala
index 9f93a09..68c6360 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-id.vala
@@ -10,7 +10,7 @@ private class Geary.ImapEngine.ListEmailByID : Geary.ImapEngine.AbstractListEmai
     private int fulfilled_count = 0;
     private Imap.UID? initial_uid = null;
     
-    public ListEmailByID(GenericFolder owner, ImapDB.EmailIdentifier? initial_id, int count,
+    public ListEmailByID(MinimalFolder owner, ImapDB.EmailIdentifier? initial_id, int count,
         Geary.Email.Field required_fields, Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator,
         EmailCallback? cb, Cancellable? cancellable) {
         base ("ListEmailByID", owner, required_fields, flags, accumulator, cb, cancellable);
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala
index 5a01e45..a4d354f 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-list-email-by-sparse-id.vala
@@ -7,7 +7,7 @@
 private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.AbstractListEmail {
     private Gee.HashSet<ImapDB.EmailIdentifier> ids = new Gee.HashSet<ImapDB.EmailIdentifier>();
     
-    public ListEmailBySparseID(GenericFolder owner, Gee.Collection<ImapDB.EmailIdentifier> ids,
+    public ListEmailBySparseID(MinimalFolder owner, Gee.Collection<ImapDB.EmailIdentifier> ids,
         Geary.Email.Field required_fields, Folder.ListFlags flags, Gee.List<Geary.Email>? accumulator,
         EmailCallback cb, Cancellable? cancellable) {
         base ("ListEmailBySparseID", owner, required_fields, flags, accumulator, cb, cancellable);
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
index b4b09af..807c1e3 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-mark-email.vala
@@ -5,14 +5,14 @@
  */
 
 private class Geary.ImapEngine.MarkEmail : Geary.ImapEngine.SendReplayOperation {
-    private GenericFolder engine;
+    private MinimalFolder engine;
     private Gee.List<Geary.EmailIdentifier> to_mark = new Gee.ArrayList<Geary.EmailIdentifier>();
     private Geary.EmailFlags? flags_to_add;
     private Geary.EmailFlags? flags_to_remove;
     private Gee.Map<ImapDB.EmailIdentifier, Geary.EmailFlags>? original_flags = null;
     private Cancellable? cancellable;
     
-    public MarkEmail(GenericFolder engine, Gee.List<Geary.EmailIdentifier> to_mark, 
+    public MarkEmail(MinimalFolder engine, Gee.List<Geary.EmailIdentifier> to_mark, 
         Geary.EmailFlags? flags_to_add, Geary.EmailFlags? flags_to_remove, 
         Cancellable? cancellable = null) {
         base("MarkEmail");
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
index e43f625..0e17211 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-move-email.vala
@@ -5,14 +5,14 @@
  */
 
 private class Geary.ImapEngine.MoveEmail : Geary.ImapEngine.SendReplayOperation {
-    private GenericFolder engine;
+    private MinimalFolder engine;
     private Gee.List<ImapDB.EmailIdentifier> to_move = new Gee.ArrayList<ImapDB.EmailIdentifier>();
     private Geary.FolderPath destination;
     private Cancellable? cancellable;
     private Gee.Set<ImapDB.EmailIdentifier>? moved_ids = null;
     private int original_count = 0;
 
-    public MoveEmail(GenericFolder engine, Gee.List<ImapDB.EmailIdentifier> to_move, 
+    public MoveEmail(MinimalFolder engine, Gee.List<ImapDB.EmailIdentifier> to_move, 
         Geary.FolderPath destination, Cancellable? cancellable = null) {
         base("MoveEmail");
 
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
index 3acf71b..09e7fca 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-remove-email.vala
@@ -5,13 +5,13 @@
  */
 
 private class Geary.ImapEngine.RemoveEmail : Geary.ImapEngine.SendReplayOperation {
-    private GenericFolder engine;
+    private MinimalFolder engine;
     private Gee.List<ImapDB.EmailIdentifier> to_remove = new Gee.ArrayList<ImapDB.EmailIdentifier>();
     private Cancellable? cancellable;
     private Gee.Set<ImapDB.EmailIdentifier>? removed_ids = null;
     private int original_count = 0;
     
-    public RemoveEmail(GenericFolder engine, Gee.List<ImapDB.EmailIdentifier> to_remove,
+    public RemoveEmail(MinimalFolder engine, Gee.List<ImapDB.EmailIdentifier> to_remove,
         Cancellable? cancellable = null) {
         base("RemoveEmail");
         
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala
index 32de08b..eb5f452 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-replay-append.vala
@@ -5,11 +5,11 @@
  */
 
 private class Geary.ImapEngine.ReplayAppend : Geary.ImapEngine.ReceiveReplayOperation {
-    public GenericFolder owner;
+    public MinimalFolder owner;
     public int remote_count;
     public Gee.List<Imap.SequenceNumber> positions;
     
-    public ReplayAppend(GenericFolder owner, int remote_count, Gee.List<Imap.SequenceNumber> positions) {
+    public ReplayAppend(MinimalFolder owner, int remote_count, Gee.List<Imap.SequenceNumber> positions) {
         base ("Append");
         
         this.owner = owner;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
index 892ded0..a06d93f 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-replay-disconnect.vala
@@ -5,10 +5,10 @@
  */
 
 private class Geary.ImapEngine.ReplayDisconnect : Geary.ImapEngine.ReceiveReplayOperation {
-    public GenericFolder owner;
+    public MinimalFolder owner;
     public Imap.ClientSession.DisconnectReason reason;
     
-    public ReplayDisconnect(GenericFolder owner, Imap.ClientSession.DisconnectReason reason) {
+    public ReplayDisconnect(MinimalFolder owner, Imap.ClientSession.DisconnectReason reason) {
         base ("Disconnect");
         
         this.owner = owner;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala
index be4a24f..2339778 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-replay-removal.vala
@@ -5,11 +5,11 @@
  */
 
 private class Geary.ImapEngine.ReplayRemoval : Geary.ImapEngine.ReceiveReplayOperation {
-    public GenericFolder owner;
+    public MinimalFolder owner;
     public int remote_count;
     public Imap.SequenceNumber position;
     
-    public ReplayRemoval(GenericFolder owner, int remote_count, Imap.SequenceNumber position) {
+    public ReplayRemoval(MinimalFolder owner, int remote_count, Imap.SequenceNumber position) {
         base ("Removal");
         
         this.owner = owner;
diff --git a/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala 
b/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
index 6b3f67c..8d03415 100644
--- a/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
+++ b/src/engine/imap-engine/replay-ops/imap-engine-server-search-email.vala
@@ -13,7 +13,7 @@
 private class Geary.ImapEngine.ServerSearchEmail : Geary.ImapEngine.AbstractListEmail {
     private Imap.SearchCriteria criteria;
     
-    public ServerSearchEmail(GenericFolder owner, Imap.SearchCriteria criteria, Geary.Email.Field 
required_fields,
+    public ServerSearchEmail(MinimalFolder owner, Imap.SearchCriteria criteria, Geary.Email.Field 
required_fields,
         Gee.List<Geary.Email>? accumulator, Cancellable? cancellable) {
         // OLDEST_TO_NEWEST used for vector expansion, if necessary
         base ("ServerSearchEmail", owner, required_fields, Geary.Folder.ListFlags.OLDEST_TO_NEWEST,
diff --git a/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala 
b/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
index 6b2b86e..9c616a1 100644
--- a/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
+++ b/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
@@ -48,27 +48,12 @@ private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount {
         }
     }
     
-    protected override GenericFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
+    protected override MinimalFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
         ImapDB.Account local_account, ImapDB.Folder local_folder) {
         SpecialFolderType special_folder_type = special_map.has_key(path) ? special_map.get(path)
             : Geary.SpecialFolderType.NONE;
-        switch (special_folder_type) {
-            case SpecialFolderType.SENT:
-                return new GenericSentMailFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-            
-            case SpecialFolderType.TRASH:
-                return new GenericTrashFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-            
-            case SpecialFolderType.DRAFTS:
-                return new GenericDraftsFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-            
-            default:
-                return new YahooFolder(this, remote_account, local_account, local_folder,
-                    special_folder_type);
-        }
+        return new YahooFolder(this, remote_account, local_account, local_folder,
+            special_folder_type);
     }
 }
 
diff --git a/src/engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala 
b/src/engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala
index 430cbc5..2841d99 100644
--- a/src/engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala
+++ b/src/engine/imap-engine/yahoo/imap-engine-yahoo-folder.vala
@@ -4,15 +4,10 @@
  * (version 2.1 or later).  See the COPYING file in this distribution.
  */
 
-private class Geary.ImapEngine.YahooFolder : GenericFolder, Geary.FolderSupport.Remove {
+private class Geary.ImapEngine.YahooFolder : GenericFolder {
     public YahooFolder(YahooAccount account, Imap.Account remote, ImapDB.Account local,
         ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
         base (account, remote, local, local_folder, special_folder_type);
     }
-    
-    public async void remove_email_async(Gee.List<Geary.EmailIdentifier> email_ids,
-        Cancellable? cancellable = null) throws Error {
-        yield expunge_email_async(email_ids, cancellable);
-    }
 }
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]