[geary/mjog/invert-folder-class-hierarchy: 361/362] Geary.Engine: Allow applications to specify min email download fields




commit 9e39b828766d8477bd4a3ea2a5f55b58c10ed5af
Author: Michael Gratton <mike vee net>
Date:   Tue Feb 23 22:48:25 2021 +1100

    Geary.Engine: Allow applications to specify min email download fields
    
    Rather than hard-coding a minimum number of email fields to be
    downloaded for new mail before signalling its existence, let the
    application specify it so it can rely on certain fields to always
    be available.
    
    Adds a new `Engine.minimum_email_fields` property, ensure this is
    observed when downloading email. Clean up related consts and API docs.

 src/client/application/application-client.vala     |  6 ++++
 src/engine/api/geary-engine.vala                   | 37 ++++++++++++++++++++++
 src/engine/app/app-conversation-monitor.vala       | 25 ++++++++-------
 src/engine/app/app-search-folder.vala              | 21 +++++++++++-
 src/engine/imap-db/imap-db-account.vala            | 34 ++++++++++++++++++--
 src/engine/imap-db/imap-db-folder.vala             | 21 +-----------
 .../imap-engine/imap-engine-minimal-folder.vala    |  9 ++++--
 .../replay-ops/imap-engine-replay-append.vala      |  4 ++-
 .../common/common-fts-search-query-test.vala       |  1 +
 test/engine/imap-db/imap-db-account-test.vala      |  1 +
 test/engine/imap-db/imap-db-folder-test.vala       |  1 +
 .../imap-engine-account-based-test.vala            |  1 +
 12 files changed, 122 insertions(+), 39 deletions(-)
---
diff --git a/src/client/application/application-client.vala b/src/client/application/application-client.vala
index f76e291b9..3d1547cae 100644
--- a/src/client/application/application-client.vala
+++ b/src/client/application/application-client.vala
@@ -382,6 +382,12 @@ public class Application.Client : Gtk.Application {
         Hdy.init();
 
         this.engine = new Geary.Engine(get_resource_directory());
+        this.engine.minimum_email_fields = (
+            Geary.App.ConversationMonitor.REQUIRED_FIELDS |
+            Geary.App.SearchFolder.REQUIRED_FIELDS |
+            ConversationListStore.REQUIRED_FIELDS
+        );
+
         this.config = new Configuration(SCHEMA_ID);
         this.autostart = new StartupManager(
             this.config, this.get_desktop_directory()
diff --git a/src/engine/api/geary-engine.vala b/src/engine/api/geary-engine.vala
index 69089c08a..ab204df56 100644
--- a/src/engine/api/geary-engine.vala
+++ b/src/engine/api/geary-engine.vala
@@ -79,6 +79,42 @@ public class Geary.Engine : BaseObject {
     /** Location of the directory containing shared resource files. */
     public File resource_dir { get; private set; }
 
+    /**
+     * The minimum set of fields to be fulfilled when downloading email.
+     *
+     * The value of this setting determines how much of an email will
+     * be initially downloaded from a remote when the email is first
+     * delivered to a mailbox, and the minimum amount that will be
+     * stored in local storage, regardless of caching used for the
+     * email body and attachments.
+     *
+     * When a remote server is delivered new email, the engine will
+     * not notify about new mail being {@link
+     * Account.email_appended_to_folder} or {@link
+     * Account.email_inserted_into_folder} until enough of the email
+     * has been downloaded such that the fields given here have been
+     * fulfilled.
+     *
+     * Setting more fields here will require more data to be
+     * downloaded when an email first arrives, causing notifications
+     * to be delayed, potentially incurring higher data costs, and
+     * causing more storage to be used for the email. Setting fewer
+     * fields may cause more round-trips to the server to downloaded
+     * additional parts of the email if not enough is present to
+     * display to the user.
+     *
+     * Some engine-provided applications components such as the {@link
+     * App.ConversationMonitor} and {@link App.SearchFolder} have
+     * specific requirements, if using these components be sure to
+     * include their required fields (for example: {@link
+     * App.ConversationMonitor.REQUIRED_FIELDS}) to this property. See
+     * those classes for details. Further, the Engine has its own
+     * internal minimum requirements for de-duplication of email and
+     * so on, and hence will always download a minimum to satisfy
+     * those.
+     */
+    public Email.Field minimum_email_fields { get; set; default = 0; }
+
     private bool is_open = true;
     private Gee.List<Account> accounts = new Gee.ArrayList<Account>();
 
@@ -200,6 +236,7 @@ public class Geary.Engine : BaseObject {
 
         ImapDB.Account local = new ImapDB.Account(
             config,
+            this.minimum_email_fields,
             config.data_dir,
             this.resource_dir.get_child("sql")
         );
diff --git a/src/engine/app/app-conversation-monitor.vala b/src/engine/app/app-conversation-monitor.vala
index c773ac399..7ab5af759 100644
--- a/src/engine/app/app-conversation-monitor.vala
+++ b/src/engine/app/app-conversation-monitor.vala
@@ -26,25 +26,25 @@
  * additional scan operations to be executed as needed to fill the new
  * window size.
  *
- * If the folder is backed by a remote mailbox, scans will be
- * local-only if the remote is not open so as to not block. However
- * this means any messages (and their conversations) that are not
- * sufficiently complete to satisfy both the monitor's and the owner's
- * email field requirements will not be found. If or when the folder
- * does open a remote connection, the folder will be re-scanned to
- * ensure any missing messages are picked up.
- *
  * The monitor will also keep track of messages being appended or
  * removed account-wide, so that known conversations can be updated as
  * needed.
+ *
+ * If using this class in your application, ensure {@link
+ * REQUIRED_FIELDS} is added to {@link Engine.minimum_email_fields} so
+ * that email can be processed before being completely downloaded.
  */
 public class Geary.App.ConversationMonitor : BaseObject, Logging.Source {
 
     /**
-     * The fields Conversations require to thread emails together.
+     * The minimum fields required by the conversation monitor.
+     *
+     * These fields will be retrieved regardless of the
+     * `required_fields` parameter passed to the constructor, and
+     * should be generally available for email when first downloaded
+     * from the remote.
      *
-     * These fields will be retrieved regardless of the Field
-     * parameter passed to the constructor.
+     * @see Engine.minimum_email_fields
      */
     public const Geary.Email.Field REQUIRED_FIELDS = (
         // Required for linking email into conversations
@@ -290,7 +290,8 @@ public class Geary.App.ConversationMonitor : BaseObject, Logging.Source {
      *
      * @param base_folder a Folder to monitor for conversations
      * @param required_fields See {@link Geary.Folder}
-     * @param min_window_count Minimum number of conversations that will be loaded
+     * @param min_window_count Minimum number of conversations that
+     * will be loaded
      */
     public ConversationMonitor(Folder base_folder,
                                Email.Field required_fields,
diff --git a/src/engine/app/app-search-folder.vala b/src/engine/app/app-search-folder.vala
index ea3771e66..903c5800a 100644
--- a/src/engine/app/app-search-folder.vala
+++ b/src/engine/app/app-search-folder.vala
@@ -12,6 +12,10 @@
  * This uses the search methods on {@link Account} to implement the
  * search, then collects search results and presents them via the
  * folder interface.
+ *
+ * If using this class in your application, ensure {@link
+ * REQUIRED_FIELDS} is added to {@link Engine.minimum_email_fields} so
+ * that email can be processed before being completely downloaded.
  */
 public class Geary.App.SearchFolder : BaseObject,
     Logging.Source,
@@ -19,6 +23,21 @@ public class Geary.App.SearchFolder : BaseObject,
     FolderSupport.Remove {
 
 
+    /**
+     * The minimum fields required by the search folder.
+     *
+     * These fields will be retrieved regardless of the
+     * `required_fields` parameter passed to the constructor, and
+     * should be generally available for email when first downloaded
+     * from the remote.
+     *
+     * @see Engine.minimum_email_fields
+     */
+    public const Geary.Email.Field REQUIRED_FIELDS = (
+        // Required for ordering conversations
+        PROPERTIES
+    );
+
     /** Number of messages to include in the initial search. */
     public const int MAX_RESULT_EMAILS = 1000;
 
@@ -552,7 +571,7 @@ public class Geary.App.SearchFolder : BaseObject,
                 Gee.Collection<Email> email_results =
                     yield this.account.get_multiple_email_by_id(
                         id_results,
-                        PROPERTIES,
+                        REQUIRED_FIELDS,
                         NONE,
                         cancellable
                     );
diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala
index 978195ad9..8acaa31e0 100644
--- a/src/engine/imap-db/imap-db-account.vala
+++ b/src/engine/imap-db/imap-db-account.vala
@@ -8,6 +8,22 @@
 
 private class Geary.ImapDB.Account : BaseObject {
 
+
+    /** Minimum fields required for email to be stored in the database. */
+    internal const Email.Field MIN_REQUIRED_FIELDS = (
+        // Required for primary duplicate detection size and received date
+        PROPERTIES |
+        // Required for secondary duplicate detection via UID
+        REFERENCES |
+        // Required to ensure the unread count is up to date and so
+        // that when moving a message, the new copy turns back up as
+        // being not deleted.
+        FLAGS
+    );
+
+    /** Fields required for a message to be considered for full-text indexing. */
+    private const Email.Field REQUIRED_FTS_FIELDS = Email.REQUIRED_FOR_MESSAGE;
+
     // Storage path names
     private const string DB_FILENAME = "geary.db";
     private const string ATTACHMENTS_DIR = "attachments";
@@ -47,6 +63,13 @@ private class Geary.ImapDB.Account : BaseObject {
     /** The backing database for the account. */
     public ImapDB.Database db { get; private set; }
 
+    /**
+     * Minimum email field set to be downloaded for new email.
+     *
+     * @see Engine.minimum_email_fields
+     */
+    public Email.Field required_email_fields { get; private set; }
+
     internal AccountInformation account_information { get; private set; }
 
     private string name;
@@ -57,6 +80,7 @@ private class Geary.ImapDB.Account : BaseObject {
     private Cancellable? background_cancellable = null;
 
     public Account(AccountInformation config,
+                   Email.Field required_email_fields,
                    GLib.File data_dir,
                    GLib.File schema_dir) {
         this.account_information = config;
@@ -64,6 +88,8 @@ private class Geary.ImapDB.Account : BaseObject {
         this.db_file = data_dir.get_child(DB_FILENAME);
         this.attachments_dir = data_dir.get_child(ATTACHMENTS_DIR);
 
+        update_required_email_fields(required_email_fields);
+
         this.db = new ImapDB.Database(
             this.db_file,
             schema_dir,
@@ -73,6 +99,10 @@ private class Geary.ImapDB.Account : BaseObject {
         );
     }
 
+    public void update_required_email_fields(Email.Field required_fields) {
+        this.required_email_fields = MIN_REQUIRED_FIELDS | required_fields;
+    }
+
     public async void open_async(GLib.Cancellable? cancellable)
         throws GLib.Error {
         if (this.db.is_open) {
@@ -814,8 +844,8 @@ private class Geary.ImapDB.Account : BaseObject {
                     var stmt = cx.prepare(
                         "SELECT id FROM MessageTable WHERE (fields & ?) = ?"
                     );
-                    stmt.bind_uint(0, Geary.ImapDB.Folder.REQUIRED_FTS_FIELDS);
-                    stmt.bind_uint(1, Geary.ImapDB.Folder.REQUIRED_FTS_FIELDS);
+                    stmt.bind_uint(0, REQUIRED_FTS_FIELDS);
+                    stmt.bind_uint(1, REQUIRED_FTS_FIELDS);
                     result = stmt.exec(cancellable);
                     while (!result.finished) {
                         message_ids.add(result.rowid_at(0));
diff --git a/src/engine/imap-db/imap-db-folder.vala b/src/engine/imap-db/imap-db-folder.vala
index 43646b5a0..455271f2f 100644
--- a/src/engine/imap-db/imap-db-folder.vala
+++ b/src/engine/imap-db/imap-db-folder.vala
@@ -17,25 +17,6 @@
 
 private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
 
-    /**
-     * Fields required for a message to be stored in the database.
-     */
-    public const Geary.Email.Field REQUIRED_FIELDS = (
-        // Required for primary duplicate detection done with properties
-        Email.Field.PROPERTIES |
-        // Required for secondary duplicate detection via UID
-        Email.Field.REFERENCES |
-        // Required to ensure the unread count is up to date and so
-        // that when moving a message, the new copy turns back up as
-        // being not deleted.
-        Email.Field.FLAGS
-    );
-
-    /**
-     * Fields required for a message to be considered for full-text indexing.
-     */
-    public const Geary.Email.Field REQUIRED_FTS_FIELDS = Geary.Email.REQUIRED_FOR_MESSAGE;
-
     private const int LIST_EMAIL_WITH_MESSAGE_CHUNK_COUNT = 10;
     private const int LIST_EMAIL_METADATA_COUNT = 100;
     private const int LIST_EMAIL_FIELDS_CHUNK_COUNT = 500;
@@ -1551,7 +1532,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
         throws Error {
         int64 id = -1;
         // if fields not present, then no duplicate can reliably be found
-        if (!email.fields.is_all_set(REQUIRED_FIELDS)) {
+        if (!email.fields.is_all_set(Account.MIN_REQUIRED_FIELDS)) {
             debug(
                 "%s: Unable to detect duplicates for %s, fields available: %s",
                 this.to_string(),
diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala 
b/src/engine/imap-engine/imap-engine-minimal-folder.vala
index 0831ced82..b83023d2f 100644
--- a/src/engine/imap-engine/imap-engine-minimal-folder.vala
+++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala
@@ -675,8 +675,11 @@ private class Geary.ImapEngine.MinimalFolder : BaseObject,
             // detection)
             Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.uid_sparse(remote_uids);
             foreach (Imap.MessageSet msg_set in msg_sets) {
-                Gee.List<Geary.Email>? list = yield session.list_email_async(msg_set,
-                    ImapDB.Folder.REQUIRED_FIELDS, cancellable);
+                Gee.List<Geary.Email>? list = yield session.list_email_async(
+                    msg_set,
+                    this._account.local.required_email_fields,
+                    cancellable
+                );
                 if (list != null && list.size > 0)
                     to_create.add_all(list);
             }
@@ -1254,7 +1257,7 @@ private class Geary.ImapEngine.MinimalFolder : BaseObject,
 
         Gee.List<Geary.Email>? list = yield remote.list_email_async(
             msg_set,
-            ImapDB.Folder.REQUIRED_FIELDS,
+            this._account.local.required_email_fields,
             cancellable
         );
 
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 0d3676a96..4850e498b 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
@@ -80,7 +80,9 @@ private class Geary.ImapEngine.ReplayAppend : Geary.ImapEngine.ReplayOperation {
         Gee.List<Imap.MessageSet> msg_sets = Imap.MessageSet.sparse(this.positions);
         foreach (Imap.MessageSet msg_set in msg_sets) {
             Gee.List<Geary.Email>? list = yield remote.list_email_async(
-                msg_set, ImapDB.Folder.REQUIRED_FIELDS, this.cancellable
+                msg_set,
+                ((GenericAccount) this.owner.account).local.required_email_fields,
+                this.cancellable
             );
             if (list != null && list.size > 0) {
                 debug("%s do_replay_appended_message: %d new messages in %s", to_string(),
diff --git a/test/engine/common/common-fts-search-query-test.vala 
b/test/engine/common/common-fts-search-query-test.vala
index 790f89954..f9ce7e0d5 100644
--- a/test/engine/common/common-fts-search-query-test.vala
+++ b/test/engine/common/common-fts-search-query-test.vala
@@ -37,6 +37,7 @@ public class Geary.FtsSearchQueryTest : TestCase {
 
         this.account = new ImapDB.Account(
             config,
+            NONE,
             this.tmp_dir,
             GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql")
         );
diff --git a/test/engine/imap-db/imap-db-account-test.vala b/test/engine/imap-db/imap-db-account-test.vala
index 0eafb7b2e..ca306c94b 100644
--- a/test/engine/imap-db/imap-db-account-test.vala
+++ b/test/engine/imap-db/imap-db-account-test.vala
@@ -43,6 +43,7 @@ class Geary.ImapDB.AccountTest : TestCase {
 
         this.account = new Account(
             config,
+            NONE,
             this.tmp_dir,
             GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql")
         );
diff --git a/test/engine/imap-db/imap-db-folder-test.vala b/test/engine/imap-db/imap-db-folder-test.vala
index dc2fff657..d6a54962f 100644
--- a/test/engine/imap-db/imap-db-folder-test.vala
+++ b/test/engine/imap-db/imap-db-folder-test.vala
@@ -43,6 +43,7 @@ class Geary.ImapDB.FolderTest : TestCase {
 
         this.account = new Account(
             config,
+            NONE,
             this.tmp_dir,
             GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql")
         );
diff --git a/test/engine/imap-engine/imap-engine-account-based-test.vala 
b/test/engine/imap-engine/imap-engine-account-based-test.vala
index a8a902e96..21818e15e 100644
--- a/test/engine/imap-engine/imap-engine-account-based-test.vala
+++ b/test/engine/imap-engine/imap-engine-account-based-test.vala
@@ -53,6 +53,7 @@ internal abstract class Geary.ImapEngine.AccountBasedTest : TestCase {
 
         this.local_account = new ImapDB.Account(
             config,
+            NONE,
             this.tmp_dir,
             GLib.File.new_for_path(_SOURCE_ROOT_DIR).get_child("sql")
         );


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