[geary/wip/794700-lazy-load-conversations: 1/3] Lazily load message bodies in conversation viewer



commit 7cb7b2e551b711e3ad7313f3aaea8fd917dc08df
Author: Michael Gratton <mike vee net>
Date:   Wed Jan 16 14:43:07 2019 +1100

    Lazily load message bodies in conversation viewer
    
    This reduces resource consumption when loading large conversations.

 .../conversation-viewer/conversation-email.vala    | 70 ++++++++++------------
 .../conversation-viewer/conversation-list-box.vala | 13 ++--
 .../conversation-viewer/conversation-message.vala  |  4 ++
 3 files changed, 40 insertions(+), 47 deletions(-)
---
diff --git a/src/client/conversation-viewer/conversation-email.vala 
b/src/client/conversation-viewer/conversation-email.vala
index d0e160d0..2e3852ab 100644
--- a/src/client/conversation-viewer/conversation-email.vala
+++ b/src/client/conversation-viewer/conversation-email.vala
@@ -255,6 +255,9 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
     /** Determines if all message's web views have finished loading. */
     public bool message_bodies_loaded { get; private set; default = false; }
 
+    // Cancellable to use when loading message content
+    private GLib.Cancellable load_cancellable;
+
     // Contacts for the email's account
     private Geary.ContactStore contact_store;
 
@@ -268,7 +271,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
 
     // A subset of the message's attachments that are displayed in the
     // attachments view
-    Gee.Collection<Geary.Attachment> displayed_attachments =
+    private Gee.Collection<Geary.Attachment> displayed_attachments =
          new Gee.LinkedList<Geary.Attachment>();
 
     // Message-specific actions
@@ -371,11 +374,13 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
                              Geary.ContactStore contact_store,
                              Configuration config,
                              bool is_sent,
-                             bool is_draft) {
+                             bool is_draft,
+                             GLib.Cancellable load_cancellable) {
         base_ref();
         this.email = email;
         this.contact_store = contact_store;
         this.config = config;
+        this.load_cancellable = load_cancellable;
 
         if (is_sent) {
             get_style_context().add_class(SENT_CLASS);
@@ -520,33 +525,12 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
     }
 
     /**
-     * Starts loading the complete email.
-     *
-     * This method will load the avatar and message body for the
-     * primary message and any attached messages, as well as
-     * attachment names, types and icons.
+     * Loads avatars for the email's messages.
      */
-    public async void start_loading(Application.AvatarStore avatars,
-                                    Cancellable load_cancelled) {
+    public async void load_avatars(Application.AvatarStore store) {
         foreach (ConversationMessage view in this)  {
-            if (load_cancelled.is_cancelled()) {
-                break;
-            }
-            yield view.load_message_body(load_cancelled);
-            view.load_avatar.begin(avatars, load_cancelled);
-        }
-
-        // Only load attachments once the web views have finished
-        // loading, since we want to know if any attachments marked as
-        // being inline were actually not displayed inline, and hence
-        // need to be displayed as if they were attachments.
-        if (!load_cancelled.is_cancelled()) {
-            if (this.message_bodies_loaded) {
-                yield load_attachments(load_cancelled);
-            } else {
-                this.notify["message-bodies-loaded"].connect(() => {
-                        load_attachments.begin(load_cancelled);
-                    });
+            if (!this.load_cancellable.is_cancelled()) {
+                yield view.load_avatar(store, this.load_cancellable);
             }
         }
     }
@@ -572,13 +556,15 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
      * Shows the complete message: headers, body and attachments.
      */
     public void expand_email(bool include_transitions=true) {
-        is_collapsed = false;
+        this.is_collapsed = false;
         update_email_state();
-        attachments_button.set_sensitive(true);
-        email_menubutton.set_sensitive(true);
-        primary_message.show_message_body(include_transitions);
-        foreach (ConversationMessage attached in this._attached_messages) {
-            attached.show_message_body(include_transitions);
+        this.attachments_button.set_sensitive(true);
+        this.email_menubutton.set_sensitive(true);
+        foreach (ConversationMessage message in this) {
+            message.show_message_body(include_transitions);
+            if (!message.body_load_started) {
+                message.load_message_body.begin(this.load_cancellable);
+            }
         }
     }
 
@@ -682,10 +668,16 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
                         break;
                     }
                 }
-                if (all_loaded == true && !this.message_bodies_loaded) {
-                    // Only update the property value if not already
-                    // true
+                if (all_loaded && !this.message_bodies_loaded) {
                     this.message_bodies_loaded = true;
+                    // Only load attachments once the web views have
+                    // finished loading, since we want to know if any
+                    // attachments marked as being inline were
+                    // actually not displayed inline, and hence need
+                    // to be displayed as if they were attachments.
+                    if (!this.load_cancellable.is_cancelled()) {
+                        this.load_attachments.begin();
+                    }
                 }
             });
         view.web_view.selection_changed.connect((has_selection) => {
@@ -726,7 +718,7 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
         }
     }
 
-    private async void load_attachments(Cancellable load_cancelled) {
+    private async void load_attachments() {
         // Determine if we have any attachments to be displayed. This
         // relies on the primary and any attached message bodies
         // having being already loaded, so that we know which
@@ -765,12 +757,12 @@ public class ConversationEmail : Gtk.Box, Geary.BaseInterface {
             }
 
             foreach (Geary.Attachment attachment in this.displayed_attachments) {
-                if (load_cancelled.is_cancelled()) {
+                if (this.load_cancellable.is_cancelled()) {
                     return;
                 }
                 AttachmentView view = new AttachmentView(attachment);
                 this.attachments_view.add(view);
-                yield view.load_icon(load_cancelled);
+                yield view.load_icon(this.load_cancellable);
             }
         }
     }
diff --git a/src/client/conversation-viewer/conversation-list-box.vala 
b/src/client/conversation-viewer/conversation-list-box.vala
index 9c4e767d..5822afc6 100644
--- a/src/client/conversation-viewer/conversation-list-box.vala
+++ b/src/client/conversation-viewer/conversation-list-box.vala
@@ -492,9 +492,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
 
             // Start the first expanded row loading before any others,
             // scroll the view to it when its done
-            yield first_expanded_row.view.start_loading(
-                this.avatar_store, this.cancellable
-            );
+            yield first_expanded_row.view.load_avatars(this.avatar_store);
             first_expanded_row.should_scroll.connect(scroll_to);
             first_expanded_row.enable_should_scroll();
 
@@ -503,9 +501,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
                     if (!this.cancellable.is_cancelled()) {
                         EmailRow? row = child as EmailRow;
                         if (row != null && row != first_expanded_row) {
-                            row.view.start_loading.begin(
-                                this.avatar_store, this.cancellable
-                            );
+                            row.view.load_avatars.begin(this.avatar_store);
                         }
                     }
                 });
@@ -762,7 +758,7 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
         if (!this.cancellable.is_cancelled()) {
             EmailRow row = add_email(full_email);
             update_first_last_row();
-            yield row.view.start_loading(this.avatar_store, this.cancellable);
+            yield row.view.load_avatars(this.avatar_store);
         }
     }
 
@@ -789,7 +785,8 @@ public class ConversationListBox : Gtk.ListBox, Geary.BaseInterface {
             this.contact_store,
             this.config,
             is_sent,
-            is_draft
+            is_draft,
+            this.cancellable
         );
         view.mark_email.connect(on_mark_email);
         view.mark_email_from_here.connect(on_mark_email_from_here);
diff --git a/src/client/conversation-viewer/conversation-message.vala 
b/src/client/conversation-viewer/conversation-message.vala
index 60faa8ac..db5fabb6 100644
--- a/src/client/conversation-viewer/conversation-message.vala
+++ b/src/client/conversation-viewer/conversation-message.vala
@@ -145,6 +145,9 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
     /** The specific RFC822 message displayed by this view. */
     public Geary.RFC822.Message message { get; private set; }
 
+    /** Determines if the message body as started loading. */
+    public bool body_load_started { get; private set; default = false; }
+
     /** Box containing the preview and full header widgets.  */
     [GtkChild]
     internal Gtk.Grid summary;
@@ -472,6 +475,7 @@ public class ConversationMessage : Gtk.Grid, Geary.BaseInterface {
      * Starts loading the message body in the HTML view.
      */
     public async void load_message_body(Cancellable load_cancelled) {
+        this.body_load_started = true;
         string? body_text = null;
         try {
             body_text = (this.message.has_html_body())


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