[geary/mjog/search-refinement: 7/14] Move SearchFolder management into the client



commit b40e4e7435fd66875fde985b28e0f5295fb820ff
Author: Michael Gratton <mike vee net>
Date:   Fri Dec 13 12:12:07 2019 +1100

    Move SearchFolder management into the client
    
    Rename the search folder again to App.SearchFolder, move its id class
    into it as an inner class. Remove search folder from the engine so the
    application can manage it and it's policy in the future. Also remove
    the outbox from the accout's list of local folders, so that code can
    be removed altogether.

 po/POTFILES.in                                     |   5 +-
 src/client/application/application-controller.vala |  10 +-
 .../application/application-main-window.vala       |  45 ++---
 .../conversation-viewer/conversation-viewer.vala   |   6 +-
 .../folder-list/folder-list-search-branch.vala     |   8 +-
 src/client/folder-list/folder-list-tree.vala       |   2 +-
 src/engine/api/geary-account.vala                  |  10 +
 src/engine/api/geary-search-folder.vala            |  86 --------
 src/engine/api/geary-search-query.vala             |   4 +-
 .../app-search-folder.vala}                        | 223 ++++++++++++++++++---
 .../gmail/imap-engine-gmail-account.vala           |   4 -
 .../gmail/imap-engine-gmail-search-folder.vala     |  46 -----
 .../imap-engine/imap-engine-generic-account.vala   |  77 ++-----
 src/engine/meson.build                             |   6 +-
 src/engine/search/search-email-identifier.vala     | 129 ------------
 15 files changed, 256 insertions(+), 405 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 71229fb7..91798075 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -144,7 +144,6 @@ src/engine/api/geary-named-flags.vala
 src/engine/api/geary-problem-report.vala
 src/engine/api/geary-progress-monitor.vala
 src/engine/api/geary-revokable.vala
-src/engine/api/geary-search-folder.vala
 src/engine/api/geary-search-query.vala
 src/engine/api/geary-service-information.vala
 src/engine/api/geary-service-provider.vala
@@ -153,6 +152,7 @@ src/engine/app/app-conversation-monitor.vala
 src/engine/app/app-conversation.vala
 src/engine/app/app-draft-manager.vala
 src/engine/app/app-email-store.vala
+src/engine/app/app-search-folder.vala
 src/engine/app/conversation-monitor/app-append-operation.vala
 src/engine/app/conversation-monitor/app-conversation-operation-queue.vala
 src/engine/app/conversation-monitor/app-conversation-operation.vala
@@ -234,7 +234,6 @@ src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
 src/engine/imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala
 src/engine/imap-engine/gmail/imap-engine-gmail-drafts-folder.vala
 src/engine/imap-engine/gmail/imap-engine-gmail-folder.vala
-src/engine/imap-engine/gmail/imap-engine-gmail-search-folder.vala
 src/engine/imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala
 src/engine/imap-engine/imap-engine-account-operation.vala
 src/engine/imap-engine/imap-engine-account-processor.vala
@@ -361,8 +360,6 @@ src/engine/rfc822/rfc822-message.vala
 src/engine/rfc822/rfc822-part.vala
 src/engine/rfc822/rfc822-utils.vala
 src/engine/rfc822/rfc822.vala
-src/engine/search/search-email-identifier.vala
-src/engine/search/search-folder-impl.vala
 src/engine/smtp/smtp-authenticator.vala
 src/engine/smtp/smtp-capabilities.vala
 src/engine/smtp/smtp-client-connection.vala
diff --git a/src/client/application/application-controller.vala 
b/src/client/application/application-controller.vala
index f9e4582c..87f70a44 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -878,6 +878,7 @@ internal class Application.Controller : Geary.BaseObject {
     private async void open_account(Geary.Account account) {
         AccountContext context = new AccountContext(
             account,
+            new Geary.App.SearchFolder(account, account.local_folder_root),
             new Geary.App.EmailStore(account),
             new Application.ContactStore(account, this.folks)
         );
@@ -976,8 +977,10 @@ internal class Application.Controller : Geary.BaseObject {
 
             account_unavailable(context, is_shutdown);
 
-            context.cancellable.cancel();
+            // Stop any background processes
+            context.search.clear();
             context.contacts.close();
+            context.cancellable.cancel();
 
             // Explicitly close the inbox since we explicitly open it
             Geary.Folder? inbox = context.inbox;
@@ -1770,6 +1773,9 @@ internal class Application.AccountContext : Geary.BaseObject {
     /** The account's Inbox folder */
     public Geary.Folder? inbox = null;
 
+    /** The account's search folder */
+    public Geary.App.SearchFolder search = null;
+
     /** The account's email store */
     public Geary.App.EmailStore emails { get; private set; }
 
@@ -1818,9 +1824,11 @@ internal class Application.AccountContext : Geary.BaseObject {
 
 
     public AccountContext(Geary.Account account,
+                          Geary.App.SearchFolder search,
                           Geary.App.EmailStore emails,
                           Application.ContactStore contacts) {
         this.account = account;
+        this.search = search;
         this.emails = emails;
         this.contacts = contacts;
     }
diff --git a/src/client/application/application-main-window.vala 
b/src/client/application/application-main-window.vala
index 9f00d46a..6a2eac61 100644
--- a/src/client/application/application-main-window.vala
+++ b/src/client/application/application-main-window.vala
@@ -676,13 +676,14 @@ public class Application.MainWindow :
                      !this.folder_list.select_inbox(to_select.account))) {
                     this.folder_list.select_folder(to_select);
                 }
+
+                if (to_select.special_folder_type == SEARCH) {
+                    this.previous_non_search_folder = to_select;
+                }
             } else {
                 this.folder_list.deselect_folder();
             }
 
-            if (!(to_select is Geary.SearchFolder)) {
-                this.previous_non_search_folder = to_select;
-            }
             update_conversation_actions(NONE);
             update_title();
             this.main_toolbar.update_trash_button(
@@ -903,27 +904,25 @@ public class Application.MainWindow :
     }
 
     internal async void start_search(string query_text, bool is_interactive) {
-        var account = this.selected_account;
-        if (account != null) {
-            var folder = account.get_special_folder(
-                SEARCH
-            ) as Geary.SearchFolder;
-
+        var context = get_selected_account_context();
+        if (context != null) {
             // Stop any search in progress
             this.search_open.cancel();
             var cancellable = this.search_open = new GLib.Cancellable();
 
             var strategy = this.application.config.get_search_strategy();
             try {
-                var query = yield account.new_search_query(
+                var query = yield context.account.new_search_query(
                     query_text,
                     strategy,
                     cancellable
                 );
-                this.folder_list.set_search(this.application.engine, folder);
-                yield folder.search(query, cancellable);
+                this.folder_list.set_search(
+                    this.application.engine, context.search
+                );
+                yield context.search.search(query, cancellable);
             } catch (GLib.Error error) {
-                handle_error(account.information, error);
+                handle_error(context.account.information, error);
             }
         }
     }
@@ -934,20 +933,15 @@ public class Application.MainWindow :
         this.search_open = new GLib.Cancellable();
 
         if (this.previous_non_search_folder != null &&
-            this.selected_folder is Geary.SearchFolder) {
+            this.selected_folder.special_folder_type == SEARCH) {
             this.select_folder.begin(
                 this.previous_non_search_folder, is_interactive
             );
         }
         this.folder_list.remove_search();
 
-        if (this.selected_account != null) {
-            var folder = this.selected_account.get_special_folder(
-                SEARCH
-            ) as Geary.SearchFolder;
-            if (folder != null) {
-                folder.clear();
-            }
+        foreach (var context in this.application.controller.get_account_contexts()) {
+            context.search.clear();
         }
     }
 
@@ -1007,14 +1001,13 @@ public class Application.MainWindow :
             // that when the account is gone.
             if (this.selected_folder != null &&
                 this.selected_folder.account == to_remove.account) {
-                Geary.SearchFolder? current_search = (
-                    this.selected_folder as Geary.SearchFolder
+                bool is_account_search_active = (
+                    this.selected_folder.special_folder_type == SEARCH
                 );
 
                 yield select_folder(to_select, false);
 
-                // Clear the account's search folder if it existed
-                if (current_search != null) {
+                if (is_account_search_active) {
                     this.search_bar.set_search_text("");
                     this.search_bar.search_mode_enabled = false;
                 }
@@ -1583,7 +1576,7 @@ public class Application.MainWindow :
         if (!this.has_composer) {
             if (this.conversations.size == 0) {
                 // Let the user know if there's no available conversations
-                if (this.selected_folder is Geary.SearchFolder) {
+                if (this.selected_folder.special_folder_type == SEARCH) {
                     this.conversation_viewer.show_empty_search();
                 } else {
                     this.conversation_viewer.show_empty_folder();
diff --git a/src/client/conversation-viewer/conversation-viewer.vala 
b/src/client/conversation-viewer/conversation-viewer.vala
index 6ffeba11..3de7d72a 100644
--- a/src/client/conversation-viewer/conversation-viewer.vala
+++ b/src/client/conversation-viewer/conversation-viewer.vala
@@ -296,7 +296,7 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
             conversation.base_folder.account, null
         );
         if (query == null) {
-            var search_folder = conversation.base_folder as Geary.SearchFolder;
+            var search_folder = conversation.base_folder as Geary.App.SearchFolder;
             if (search_folder != null) {
                 query = search_folder.query;
             }
@@ -454,9 +454,9 @@ public class ConversationViewer : Gtk.Stack, Geary.BaseInterface {
             } else {
                 // Find became disabled, re-show search terms if any
                 this.current_list.search.unmark_terms();
-                Geary.SearchFolder? search_folder = (
+                Geary.App.SearchFolder? search_folder = (
                     this.current_list.conversation.base_folder
-                    as Geary.SearchFolder
+                    as Geary.App.SearchFolder
                 );
                 this.conversation_find_undo.reset();
                 if (search_folder != null) {
diff --git a/src/client/folder-list/folder-list-search-branch.vala 
b/src/client/folder-list/folder-list-search-branch.vala
index d4a0013f..038e6f6c 100644
--- a/src/client/folder-list/folder-list-search-branch.vala
+++ b/src/client/folder-list/folder-list-search-branch.vala
@@ -8,12 +8,12 @@
  * This branch is a top-level container for a search entry.
  */
 public class FolderList.SearchBranch : Sidebar.RootOnlyBranch {
-    public SearchBranch(Geary.SearchFolder folder, Geary.Engine engine) {
+    public SearchBranch(Geary.App.SearchFolder folder, Geary.Engine engine) {
         base(new SearchEntry(folder, engine));
     }
 
-    public Geary.SearchFolder get_search_folder() {
-        return (Geary.SearchFolder) ((SearchEntry) get_root()).folder;
+    public Geary.App.SearchFolder get_search_folder() {
+        return (Geary.App.SearchFolder) ((SearchEntry) get_root()).folder;
     }
 }
 
@@ -22,7 +22,7 @@ public class FolderList.SearchEntry : FolderList.AbstractFolderEntry {
     Geary.Engine engine;
     private int account_count = 0;
 
-    public SearchEntry(Geary.SearchFolder folder,
+    public SearchEntry(Geary.App.SearchFolder folder,
                        Geary.Engine engine) {
         base(folder);
         this.engine = engine;
diff --git a/src/client/folder-list/folder-list-tree.vala b/src/client/folder-list/folder-list-tree.vala
index 269ee831..f0a831d1 100644
--- a/src/client/folder-list/folder-list-tree.vala
+++ b/src/client/folder-list/folder-list-tree.vala
@@ -244,7 +244,7 @@ public class FolderList.Tree : Sidebar.Tree, Geary.BaseInterface {
     }
 
     public void set_search(Geary.Engine engine,
-                           Geary.SearchFolder search_folder) {
+                           Geary.App.SearchFolder search_folder) {
         if (search_branch != null && has_branch(search_branch)) {
             // We already have a search folder.  If it's the same one, just
             // select it.  If it's a new search folder, remove the old one and
diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala
index c98a888a..37ade9e3 100644
--- a/src/engine/api/geary-account.vala
+++ b/src/engine/api/geary-account.vala
@@ -131,6 +131,16 @@ public abstract class Geary.Account : BaseObject, Logging.Source {
      */
     public Geary.ContactStore contact_store { get; protected set; }
 
+    /**
+     * The root path for all local folders.
+     *
+     * Any local folders create by the engine or clients must use this
+     * as the root for local folders.
+     */
+    public FolderRoot local_folder_root {
+        get; private set; default = new Geary.FolderRoot("$geary-local", true);
+    }
+
     public ProgressMonitor background_progress { get; protected set; }
     public ProgressMonitor db_upgrade_monitor { get; protected set; }
     public ProgressMonitor db_vacuum_monitor { get; protected set; }
diff --git a/src/engine/api/geary-search-query.vala b/src/engine/api/geary-search-query.vala
index 4c4ec8a3..0a15ff2e 100644
--- a/src/engine/api/geary-search-query.vala
+++ b/src/engine/api/geary-search-query.vala
@@ -11,12 +11,12 @@
  *
  * New instances can be constructed via {@link
  * Account.new_search_query} and then passed to search methods on
- * {@link Account} or {@link SearchFolder}.
+ * {@link Account} or {@link App.SearchFolder}.
  *
  * @see Account.new_search_query
  * @see Account.local_search_async
  * @see Account.get_search_matches_async
- * @see SearchFolder.search
+ * @see App.SearchFolder.search
  */
 
 public abstract class Geary.SearchQuery : BaseObject {
diff --git a/src/engine/search/search-folder-impl.vala b/src/engine/app/app-search-folder.vala
similarity index 73%
rename from src/engine/search/search-folder-impl.vala
rename to src/engine/app/app-search-folder.vala
index 25009437..3e6716a9 100644
--- a/src/engine/search/search-folder-impl.vala
+++ b/src/engine/app/app-search-folder.vala
@@ -7,20 +7,21 @@
  */
 
 /**
- * A default implementation of a search folder.
+ * A folder for executing and listing an account-wide email search.
  *
- * This implementation of {@link Geary.SearchFolder} uses the search
- * methods on {@link Account} to implement account-wide email search.
+ * This uses the search methods on {@link Account} to implement the
+ * search, then collects search results and presents them via the
+ * folder interface.
  */
-internal class Geary.Search.FolderImpl :
-    Geary.SearchFolder, Geary.FolderSupport.Remove {
+public class Geary.App.SearchFolder :
+    Geary.AbstractLocalFolder, Geary.FolderSupport.Remove {
 
 
-    /** Max number of emails that can ever be in the folder. */
+    /** Number of messages to include in the initial search. */
     public const int MAX_RESULT_EMAILS = 1000;
 
     /** The canonical name of the search folder. */
-    public const string MAGIC_BASENAME = "$GearySearchFolder$";
+    public const string MAGIC_BASENAME = "$GearyAccountSearchFolder$";
 
     private const Geary.SpecialFolderType[] EXCLUDE_TYPES = {
         Geary.SpecialFolderType.SPAM,
@@ -30,6 +31,124 @@ internal class Geary.Search.FolderImpl :
     };
 
 
+    /** Internal identifier used by the search folder */
+    internal class EmailIdentifier :
+        Geary.EmailIdentifier, Gee.Comparable<EmailIdentifier> {
+
+
+        private const string VARIANT_TYPE = "(y(vx))";
+
+
+        public static int compare_descending(EmailIdentifier a, EmailIdentifier b) {
+            return b.compare_to(a);
+        }
+
+        public static Gee.Collection<Geary.EmailIdentifier> to_source_ids(
+            Gee.Collection<Geary.EmailIdentifier> ids
+        ) {
+            var engine_ids = new Gee.LinkedList<Geary.EmailIdentifier>();
+            foreach (var id in ids) {
+                var search_id = id as EmailIdentifier;
+                engine_ids.add(search_id.source_id ?? id);
+            }
+            return engine_ids;
+        }
+
+        public static Geary.EmailIdentifier to_source_id(
+            Geary.EmailIdentifier id
+        ) {
+            var search_id = id as EmailIdentifier;
+            return search_id.source_id ?? id;
+        }
+
+
+        public Geary.EmailIdentifier source_id { get; private set; }
+
+        public GLib.DateTime? date_received { get; private set; }
+
+
+        public EmailIdentifier(Geary.EmailIdentifier source_id,
+                               GLib.DateTime? date_received) {
+            this.source_id = source_id;
+            this.date_received = date_received;
+        }
+
+        public EmailIdentifier.from_variant(GLib.Variant serialised,
+                                            Account account)
+            throws EngineError.BAD_PARAMETERS {
+            if (serialised.get_type_string() != VARIANT_TYPE) {
+            throw new EngineError.BAD_PARAMETERS(
+                "Invalid serialised id type: %s", serialised.get_type_string()
+            );
+            }
+            GLib.Variant inner = serialised.get_child_value(1);
+            this(
+                account.to_email_identifier(
+                    inner.get_child_value(0).get_variant()
+                ),
+                new GLib.DateTime.from_unix_utc(
+                    inner.get_child_value(1).get_int64()
+                )
+            );
+        }
+
+        public override uint hash() {
+            return this.source_id.hash();
+        }
+
+        public override bool equal_to(Geary.EmailIdentifier other) {
+            return (
+                this.get_type() == other.get_type() &&
+                this.source_id.equal_to(((EmailIdentifier) other).source_id)
+            );
+        }
+
+        public override GLib.Variant to_variant() {
+            // Return a tuple to satisfy the API contract, add an 's' to
+            // inform GenericAccount that it's an IMAP id.
+            return new GLib.Variant.tuple(new Variant[] {
+                    new GLib.Variant.byte('s'),
+                    new GLib.Variant.tuple(new Variant[] {
+                            new GLib.Variant.variant(this.source_id.to_variant()),
+                            new GLib.Variant.int64(this.date_received.to_unix())
+                        })
+                });
+        }
+
+        public override string to_string() {
+            return "%s(%s,%lld)".printf(
+                this.get_type().name(),
+                this.source_id.to_string(),
+                this.date_received.to_unix()
+            );
+        }
+
+        public override int natural_sort_comparator(Geary.EmailIdentifier o) {
+            EmailIdentifier? other = o as EmailIdentifier;
+            if (other == null)
+                return 1;
+
+            return compare_to(other);
+        }
+
+        public virtual int compare_to(EmailIdentifier other) {
+            // if both have date received, compare on that, using stable sort if the same
+            if (date_received != null && other.date_received != null) {
+                int compare = date_received.compare(other.date_received);
+                return (compare != 0) ? compare : stable_sort_comparator(other);
+            }
+
+            // if neither have date received, fall back on stable sort
+            if (date_received == null && other.date_received == null)
+                return stable_sort_comparator(other);
+
+            // put identifiers with no date ahead of those with
+            return (date_received == null ? -1 : 1);
+        }
+
+    }
+
+
     private class FolderProperties : Geary.FolderProperties {
 
 
@@ -44,6 +163,38 @@ internal class Geary.Search.FolderImpl :
     }
 
 
+    /** {@inheritDoc} */
+    public override Account account {
+        get { return _account; }
+    }
+    private weak Account _account;
+
+    /** {@inheritDoc} */
+    public override Geary.FolderProperties properties {
+        get { return _properties; }
+    }
+    private Geary.FolderProperties _properties;
+
+    /** {@inheritDoc} */
+    public override FolderPath path {
+        get { return _path; }
+    }
+    private FolderPath? _path = null;
+
+    /**
+     * {@inheritDoc}
+     *
+     * Always returns {@link SpecialFolderType.SEARCH}.
+     */
+    public override SpecialFolderType special_folder_type {
+        get {
+            return Geary.SpecialFolderType.SEARCH;
+        }
+    }
+
+    /** The query being evaluated by this folder, if any. */
+    public Geary.SearchQuery? query { get; protected set; default = null; }
+
     // Folders that should be excluded from search
     private Gee.HashSet<Geary.FolderPath?> exclude_folders =
         new Gee.HashSet<Geary.FolderPath?>();
@@ -56,13 +207,13 @@ internal class Geary.Search.FolderImpl :
 
     private Geary.Nonblocking.Mutex result_mutex = new Geary.Nonblocking.Mutex();
 
+    private GLib.Cancellable executing = new GLib.Cancellable();
 
-    public FolderImpl(Geary.Account account, FolderRoot root) {
-        base(
-            account,
-            new FolderProperties(0, 0),
-            root.get_child(MAGIC_BASENAME, Trillian.TRUE)
-        );
+
+    public SearchFolder(Geary.Account account, FolderRoot root) {
+        this._account = account;
+        this._properties = new FolderProperties(0, 0);
+        this._path = root.get_child(MAGIC_BASENAME, Trillian.TRUE);
 
         account.folders_available_unavailable.connect(on_folders_available_unavailable);
         account.folders_special_type.connect(on_folders_special_type);
@@ -71,12 +222,12 @@ internal class Geary.Search.FolderImpl :
 
         clear_contents();
 
-        // We always want to exclude emails that don't live anywhere
-        // from search results.
+        // Always exclude emails that don't live anywhere from search
+        // results.
         exclude_orphan_emails();
     }
 
-    ~FolderImpl() {
+    ~SearchFolder() {
         account.folders_available_unavailable.disconnect(on_folders_available_unavailable);
         account.folders_special_type.disconnect(on_folders_special_type);
         account.email_locally_complete.disconnect(on_email_locally_complete);
@@ -84,17 +235,25 @@ internal class Geary.Search.FolderImpl :
     }
 
     /**
-     * Sets the keyword string for this search.
+     * Executes the given query over the account's local email.
+     *
+     * Calling this will block until the search is complete.
      */
-    public override async void search(Geary.SearchQuery query,
-                                      GLib.Cancellable? cancellable = null)
+    public async void search(SearchQuery query, GLib.Cancellable? cancellable)
         throws GLib.Error {
         int result_mutex_token = yield result_mutex.claim_async();
 
+        clear();
+
+        if (cancellable != null) {
+            GLib.Cancellable @internal = this.executing;
+            cancellable.cancelled.connect(() => { @internal.cancel(); });
+        }
+
         this.query = query;
         GLib.Error? error = null;
         try {
-            yield do_search_async(null, null, cancellable);
+            yield do_search_async(null, null, this.executing);
         } catch(Error e) {
             error = e;
         }
@@ -104,30 +263,32 @@ internal class Geary.Search.FolderImpl :
         if (error != null) {
             throw error;
         }
-
-        this.query_evaluation_complete();
     }
 
     /**
-     * Clears the search query and results.
+     * Cancels and clears the search query and results.
+     *
+     * The {@link query} property will be cleared.
      */
-    public override void clear() {
+    public void clear() {
+        this.executing.cancel();
+        this.executing = new GLib.Cancellable();
+
         var old_contents = this.contents;
         clear_contents();
         notify_email_removed(old_contents);
         notify_email_count_changed(0, Geary.Folder.CountChangeReason.REMOVED);
 
-        if (this.query != null) {
-            this.query = null;
-            this.query_evaluation_complete();
-        }
+        this.query = null;
     }
 
     /**
-     * Given a list of mail IDs, returns a set of casefolded words that match for the current
-     * search query.
+     * Returns a set of case-folded words matched by the current query.
+     *
+     * The set contains words from the given collection of email that
+     * match any of the non-negated text operators in {@link query}.
      */
-    public override async Gee.Set<string>? get_search_matches_async(
+    public async Gee.Set<string>? get_search_matches_async(
         Gee.Collection<Geary.EmailIdentifier> ids,
         GLib.Cancellable? cancellable = null
     ) throws GLib.Error {
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 8e7b3acc..0f1296db 100644
--- a/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
+++ b/src/engine/imap-engine/gmail/imap-engine-gmail-account.vala
@@ -78,8 +78,4 @@ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount {
         }
     }
 
-    protected override SearchFolder new_search_folder() {
-        return new GmailSearchFolder(this, this.local_folder_root);
-    }
-
 }
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala 
b/src/engine/imap-engine/imap-engine-generic-account.vala
index 9068da79..c94ea9f6 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -39,25 +39,12 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
     /** Local database for the account. */
     public ImapDB.Account local { get; private set; }
 
-    /**
-     * The root path for all local folders.
-     *
-     * No folder exists for this path, it merely exists to provide a
-     * common root for the paths of all local folders.
-     */
-    protected FolderRoot local_folder_root = new Geary.FolderRoot(
-        "$geary-local", true
-    );
-
     private bool open = false;
     private Cancellable? open_cancellable = null;
     private Nonblocking.Semaphore? remote_ready_lock = null;
 
-    private Geary.SearchFolder? search_folder { get; private set; default = null; }
-
-    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 Gee.Map<FolderPath,MinimalFolder> folder_map =
+        new Gee.HashMap<FolderPath,MinimalFolder>();
 
     private AccountProcessor? processor;
     private AccountSynchronizer sync;
@@ -148,16 +135,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
                 throw err;
         }
 
-        // Create/load local folders
-
-        local_only.set(this.smtp.outbox.path, this.smtp.outbox);
-
-        this.search_folder = new_search_folder();
-        local_only.set(this.search_folder.path, this.search_folder);
-
         this.open = true;
         notify_opened();
-        notify_folders_available_unavailable(sort_by_path(local_only.values), null);
 
         this.queue_operation(
             new LoadFolders(this, this.local, get_supported_special_folders())
@@ -190,7 +169,6 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
 
         // Halt internal tasks early so they stop using local and
         // remote connections.
-        this.search_folder.clear();
         this.refresh_folder_timer.reset();
         this.open_cancellable.cancel();
         this.processor.stop();
@@ -203,25 +181,16 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
 
         // Close folders and ensure they do in fact close
 
-        Gee.BidirSortedSet<Folder> locals = sort_by_path(this.local_only.values);
         Gee.BidirSortedSet<Folder> remotes = sort_by_path(this.folder_map.values);
-
-        this.local_only.clear();
         this.folder_map.clear();
-
-        notify_folders_available_unavailable(null, locals);
         notify_folders_available_unavailable(null, remotes);
 
-        foreach (Geary.Folder folder in locals) {
-            debug("Waiting for local to close: %s", folder.to_string());
-            yield folder.wait_for_close_async();
-        }
         foreach (Geary.Folder folder in remotes) {
             debug("Waiting for remote to close: %s", folder.to_string());
             yield folder.wait_for_close_async();
         }
 
-        // Close IMAP service manager
+        // Close IMAP service manager now that folders are closed
 
         try {
             yield this.imap.stop();
@@ -232,7 +201,6 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
 
         // Close local infrastructure
 
-        this.search_folder = null;
         try {
             yield local.close_async(cancellable);
         } finally {
@@ -408,7 +376,9 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
         if (type == 'i')
             return new ImapDB.EmailIdentifier.from_variant(serialised);
         if (type == 's')
-            return new Search.EmailIdentifier.from_variant(serialised, this);
+            return new App.SearchFolder.EmailIdentifier.from_variant(
+                serialised, this
+            );
         if (type == 'o')
             return new Outbox.EmailIdentifier.from_variant(serialised);
 
@@ -432,23 +402,18 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
         throws EngineError.NOT_FOUND {
         Folder? folder = this.folder_map.get(path);
         if (folder == null) {
-            folder = this.local_only.get(path);
-            if (folder == null) {
-                throw new EngineError.NOT_FOUND(
-                    "Folder not found: %s", path.to_string()
-                );
-            }
+            throw new EngineError.NOT_FOUND(
+                "Folder not found: %s", path.to_string()
+            );
         }
         return folder;
     }
 
     /** {@inheritDoc} */
     public override Gee.Collection<Folder> list_folders() {
-        Gee.HashSet<Folder> all_folders = new Gee.HashSet<Folder>();
-        all_folders.add_all(this.folder_map.values);
-        all_folders.add_all(this.local_only.values);
-
-        return all_folders;
+        var all = new Gee.HashSet<Folder>();
+        all.add_all(this.folder_map.values);
+        return all;
     }
 
     /** {@inheritDoc} */
@@ -793,16 +758,6 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
      */
     protected abstract MinimalFolder new_folder(ImapDB.Folder local_folder);
 
-    /**
-     * Constructs a concrete search folder implementation.
-     *
-     * Subclasses with specific SearchFolder implementations should
-     * override this to return the correct subclass.
-     */
-    protected virtual SearchFolder new_search_folder() {
-        return new Search.FolderImpl(this, this.local_folder_root);
-    }
-
     /** {@inheritDoc} */
     protected override void
         notify_folders_available_unavailable(Gee.BidirSortedSet<Folder>? available,
@@ -861,7 +816,6 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
 
         UpdateRemoteFolders op = new UpdateRemoteFolders(
             this,
-            this.local_only.keys,
             get_supported_special_folders()
         );
         op.completed.connect(() => {
@@ -1155,16 +1109,13 @@ internal class Geary.ImapEngine.UpdateRemoteFolders : AccountOperation {
 
 
     private weak GenericAccount generic_account;
-    private Gee.Collection<FolderPath> local_folders;
     private Geary.SpecialFolderType[] specials;
 
 
     internal UpdateRemoteFolders(GenericAccount account,
-                                 Gee.Collection<FolderPath> local_folders,
                                  Geary.SpecialFolderType[] specials) {
         base(account);
         this.generic_account = account;
-        this.local_folders = local_folders;
         this.specials = specials;
     }
 
@@ -1289,10 +1240,10 @@ internal class Geary.ImapEngine.UpdateRemoteFolders : AccountOperation {
             .filter(f => !existing_folders.has_key(f.path))
             .to_array_list();
 
-        // If path in local but not remote (and isn't local-only, i.e. the Outbox), need to remove it
+        // Remove if path in local but not remote
         Gee.ArrayList<Geary.Folder> to_remove
             = Geary.traverse<Gee.Map.Entry<FolderPath,Geary.Folder>>(existing_folders)
-            .filter(e => !remote_folders.has_key(e.key) && !this.local_folders.contains(e.key))
+            .filter(e => !remote_folders.has_key(e.key))
             .map<Geary.Folder>(e => (Geary.Folder) e.value)
             .to_array_list();
 
diff --git a/src/engine/meson.build b/src/engine/meson.build
index d98f9050..cb61c2aa 100644
--- a/src/engine/meson.build
+++ b/src/engine/meson.build
@@ -37,7 +37,6 @@ geary_engine_vala_sources = files(
   'api/geary-problem-report.vala',
   'api/geary-progress-monitor.vala',
   'api/geary-revokable.vala',
-  'api/geary-search-folder.vala',
   'api/geary-search-query.vala',
   'api/geary-service-information.vala',
   'api/geary-service-provider.vala',
@@ -47,6 +46,7 @@ geary_engine_vala_sources = files(
   'app/app-conversation-monitor.vala',
   'app/app-draft-manager.vala',
   'app/app-email-store.vala',
+  'app/app-search-folder.vala',
 
   'app/conversation-monitor/app-append-operation.vala',
   'app/conversation-monitor/app-conversation-operation-queue.vala',
@@ -197,7 +197,6 @@ geary_engine_vala_sources = files(
   'imap-engine/gmail/imap-engine-gmail-all-mail-folder.vala',
   'imap-engine/gmail/imap-engine-gmail-drafts-folder.vala',
   'imap-engine/gmail/imap-engine-gmail-folder.vala',
-  'imap-engine/gmail/imap-engine-gmail-search-folder.vala',
   'imap-engine/gmail/imap-engine-gmail-spam-trash-folder.vala',
   'imap-engine/other/imap-engine-other-account.vala',
   'imap-engine/other/imap-engine-other-folder.vala',
@@ -270,9 +269,6 @@ geary_engine_vala_sources = files(
   'rfc822/rfc822-part.vala',
   'rfc822/rfc822-utils.vala',
 
-  'search/search-email-identifier.vala',
-  'search/search-folder-impl.vala',
-
   'smtp/smtp-authenticator.vala',
   'smtp/smtp-capabilities.vala',
   'smtp/smtp-client-connection.vala',


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