[geary/wip/713432-startup] A grab-bag of changes to speed up startup time



commit c9a504c2faec4018321bdf15de2f48100e1e2c3c
Author: Jim Nelson <jim yorba org>
Date:   Wed Dec 18 18:36:12 2013 -0800

    A grab-bag of changes to speed up startup time
    
    Some of these will improve performance in general, but almost all
    would be most notable at application start.

 src/client/application/geary-controller.vala       |   20 ++++++--
 src/engine/api/geary-folder.vala                   |    6 ++-
 src/engine/imap-db/imap-db-account.vala            |    6 ++-
 .../imap-engine/imap-engine-generic-account.vala   |   52 ++++++++++++++++----
 .../imap-engine/imap-engine-generic-folder.vala    |   16 +++++--
 src/engine/imap/api/imap-account.vala              |   49 ++++++++++++++-----
 src/engine/imap/api/imap-folder.vala               |    4 +-
 .../imap/response/imap-mailbox-information.vala    |    9 +++
 .../imap/transport/imap-client-connection.vala     |    2 +-
 9 files changed, 127 insertions(+), 37 deletions(-)
---
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 1cbed51..3f11261 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -89,6 +89,7 @@ public class GearyController : Geary.BaseObject {
     private UnityLauncher? unity_launcher = null;
     private Libnotify? libnotify = null;
     private uint select_folder_timeout_id = 0;
+    private time_t next_folder_select_allowed_time = 0;
     private Geary.Folder? folder_to_select = null;
     private Geary.Nonblocking.Mutex select_folder_mutex = new Geary.Nonblocking.Mutex();
     private Geary.Account? account_to_select = null;
@@ -932,14 +933,23 @@ public class GearyController : Geary.BaseObject {
             return;
         }
         
+        folder_to_select = folder;
+        
         // To prevent the user from selecting folders too quickly, we actually
         // schedule the action to happen after a timeout instead of acting
         // directly.  If the user selects another folder during the timeout,
         // we nix the original timeout and start a new one.
-        if (select_folder_timeout_id != 0)
-            Source.remove(select_folder_timeout_id);
-        folder_to_select = folder;
-        select_folder_timeout_id = Timeout.add(SELECT_FOLDER_TIMEOUT_MSEC, on_select_folder_timeout);
+        time_t now = time_t();
+        long diff = now - next_folder_select_allowed_time;
+        if (diff < SELECT_FOLDER_TIMEOUT_MSEC) {
+            if (select_folder_timeout_id == 0)
+                select_folder_timeout_id = Timeout.add((uint) diff, on_select_folder_timeout);
+        } else {
+            do_select_folder.begin(folder_to_select, on_select_folder_completed);
+            folder_to_select = null;
+            
+            next_folder_select_allowed_time = now + SELECT_FOLDER_TIMEOUT_MSEC;
+        }
     }
     
     private bool on_select_folder_timeout() {
@@ -1013,7 +1023,7 @@ public class GearyController : Geary.BaseObject {
         
         update_ui();
         
-        current_conversations = new Geary.App.ConversationMonitor(current_folder, 
Geary.Folder.OpenFlags.NONE,
+        current_conversations = new Geary.App.ConversationMonitor(current_folder, 
Geary.Folder.OpenFlags.NO_DELAY,
             ConversationListStore.REQUIRED_FIELDS, MIN_CONVERSATION_COUNT);
         
         if (inboxes.values.contains(current_folder)) {
diff --git a/src/engine/api/geary-folder.vala b/src/engine/api/geary-folder.vala
index b7ad434..e733636 100644
--- a/src/engine/api/geary-folder.vala
+++ b/src/engine/api/geary-folder.vala
@@ -67,7 +67,11 @@ public interface Geary.Folder : BaseObject {
          * the messages (such as their flags or other metadata) may not be up-to-date
          * when the folder opens.  Not all folders will support this flag.
          */
-        FAST_OPEN;
+        FAST_OPEN,
+        /**
+         * Do not delay opening a connection to the server.
+         */
+        NO_DELAY;
         
         public bool is_any_set(OpenFlags flags) {
             return (this & flags) != 0;
diff --git a/src/engine/imap-db/imap-db-account.vala b/src/engine/imap-db/imap-db-account.vala
index 3c0407f..dab11e9 100644
--- a/src/engine/imap-db/imap-db-account.vala
+++ b/src/engine/imap-db/imap-db-account.vala
@@ -133,7 +133,11 @@ private class Geary.ImapDB.Account : BaseObject {
         background_cancellable = new Cancellable();
         
         // Kick off a background update of the search table.
-        populate_search_table_async.begin(background_cancellable);
+        Timeout.add_seconds(30, () => {
+            populate_search_table_async.begin(background_cancellable);
+            
+            return false;
+        });
         
         initialize_contacts(cancellable);
         
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala 
b/src/engine/imap-engine/imap-engine-generic-account.vala
index d26aef2..0e8aaaf 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -301,28 +301,60 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
         reschedule_folder_refresh(false);
     }
     
+    private class EnumerateLocalOperation : Nonblocking.BatchOperation {
+        public weak GenericAccount owner;
+        public Gee.HashMap<FolderPath, ImapDB.Folder>? local_folders = null;
+        
+        public EnumerateLocalOperation(GenericAccount owner) {
+            this.owner = owner;
+        }
+        
+        public override async Object? execute_async(Cancellable? cancellable) throws Error {
+            local_folders = yield owner.enumerate_local_folders_async(null, cancellable);
+            
+            return null;
+        }
+    }
+    
+    private class EnumerateRemoteOperation : Nonblocking.BatchOperation {
+        public weak GenericAccount owner;
+        public Gee.HashMap<FolderPath, Imap.Folder>? remote_folders = null;
+        
+        public EnumerateRemoteOperation(GenericAccount owner) {
+            this.owner = owner;
+        }
+        
+        public override async Object? execute_async(Cancellable? cancellable) throws Error {
+            remote_folders = yield owner.enumerate_remote_folders_async(null, cancellable);
+            
+            return null;
+        }
+    }
+    
     private async void enumerate_folders_async(Cancellable? cancellable) throws Error {
         check_open();
         
-        // get all local folders
-        Gee.HashMap<FolderPath, ImapDB.Folder> local_children = yield enumerate_local_folders_async(null,
-            cancellable);
+        EnumerateLocalOperation local_op = new EnumerateLocalOperation(this);
+        EnumerateRemoteOperation remote_op = new EnumerateRemoteOperation(this);
+        
+        Nonblocking.Batch batch = new Nonblocking.Batch();
+        batch.add(local_op);
+        batch.add(remote_op);
+        
+        yield batch.execute_all_async(cancellable);
+        batch.throw_first_exception();
         
         // convert to a list of Geary.Folder ... build_folder() also reports new folders, so this
         // gets the word out quickly
         Gee.Collection<Geary.Folder> existing_list = new Gee.ArrayList<Geary.Folder>();
-        existing_list.add_all(build_folders(local_children.values));
+        existing_list.add_all(build_folders(local_op.local_folders.values));
         existing_list.add_all(local_only.values);
         
         Gee.HashMap<FolderPath, Geary.Folder> existing_folders
             = Geary.traverse<Geary.Folder>(existing_list).to_hash_map<FolderPath>(f => f.path);
         
-        // get all remote (server) folder paths
-        Gee.HashMap<FolderPath, Imap.Folder> remote_folders = yield enumerate_remote_folders_async(null,
-            cancellable);
-        
-        // combine the two and make sure everything is up-to-date
-        yield update_folders_async(existing_folders, remote_folders, cancellable);
+        // pair the local and remote folders and make sure everything is up-to-date
+        yield update_folders_async(existing_folders, remote_op.remote_folders, cancellable);
     }
     
     private async Gee.HashMap<FolderPath, ImapDB.Folder> enumerate_local_folders_async(
diff --git a/src/engine/imap-engine/imap-engine-generic-folder.vala 
b/src/engine/imap-engine/imap-engine-generic-folder.vala
index 026a1f4..461df5e 100644
--- a/src/engine/imap-engine/imap-engine-generic-folder.vala
+++ b/src/engine/imap-engine/imap-engine-generic-folder.vala
@@ -437,6 +437,11 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
     public override async bool open_async(Geary.Folder.OpenFlags open_flags, Cancellable? cancellable = null)
         throws Error {
         if (open_count++ > 0) {
+            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;
@@ -444,12 +449,12 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
         
         this.open_flags = open_flags;
         
-        open_internal(cancellable);
+        open_internal(open_flags, cancellable);
         
         return true;
     }
     
-    private void open_internal(Cancellable? cancellable) {
+    private void open_internal(Folder.OpenFlags open_flags, Cancellable? cancellable) {
         remote_semaphore = new Geary.Nonblocking.ReportingSemaphore<bool>(false);
         
         // start the replay queue
@@ -466,7 +471,10 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
         // 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
-        start_remote_open_timer();
+        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() {
@@ -649,7 +657,7 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
         if (remote_reason.is_error()) {
             debug("Reestablishing broken connect to %s", to_string());
             
-            open_internal(null);
+            open_internal(OpenFlags.NO_DELAY, null);
             
             return;
         }
diff --git a/src/engine/imap/api/imap-account.vala b/src/engine/imap/api/imap-account.vala
index 8d71405..0cd39c4 100644
--- a/src/engine/imap/api/imap-account.vala
+++ b/src/engine/imap/api/imap-account.vala
@@ -12,9 +12,10 @@
  * that a Geary.Account implementation would need (in particular, { link Geary.ImapEngine.Account}
  * and makes them into simple async calls.
  *
- * Geary.Imap.Account does __no__ management of the { link Imap.Folder} objects it returns.  Thus,
- * calling a fetch or list operation several times in a row will return separate Folder objects
- * each time.  It is up to the higher layers of the stack to manage these objects.
+ * Geary.Imap.Account manages the { link Imap.Folder} objects it returns, but only in the sense
+ * that it will not create new instances repeatedly.  Otherwise, it does not refresh or update the
+ * Imap.Folders themselves (such as update their { link Imap.StatusData} periodically).
+ * That's the responsibility of the higher layers of the stack.
  */
 
 private class Geary.Imap.Account : BaseObject {
@@ -28,6 +29,7 @@ private class Geary.Imap.Account : BaseObject {
     private Nonblocking.Mutex cmd_mutex = new Nonblocking.Mutex();
     private Gee.HashMap<FolderPath, MailboxInformation> path_to_mailbox = new Gee.HashMap<
         FolderPath, MailboxInformation>();
+    private Gee.HashMap<FolderPath, Imap.Folder> folders = new Gee.HashMap<FolderPath, Imap.Folder>();
     private Gee.List<MailboxInformation>? list_collector = null;
     private Gee.List<StatusData>? status_collector = null;
     
@@ -143,28 +145,38 @@ private class Geary.Imap.Account : BaseObject {
     }
     
     public async bool folder_exists_async(FolderPath path, Cancellable? cancellable) throws Error {
-        return path_to_mailbox.contains(path);
+        return path_to_mailbox.has_key(path);
     }
     
     public async Imap.Folder fetch_folder_async(FolderPath path, Cancellable? cancellable)
         throws Error {
         check_open();
         
+        if (folders.has_key(path))
+            return folders.get(path);
+        
         // if not in map, use list_children_async to add it (if it exists)
-        if (!path_to_mailbox.contains(path))
+        if (!path_to_mailbox.has_key(path)) {
+            debug("Listing children to find %s", path.to_string());
             yield list_children_async(path.get_parent(), cancellable);
+        }
         
         MailboxInformation? mailbox_info = path_to_mailbox.get(path);
         if (mailbox_info == null)
             throw_not_found(path);
         
+        Imap.Folder folder;
         if (!mailbox_info.attrs.contains(MailboxAttribute.NO_SELECT)) {
             StatusData status = yield fetch_status_async(path, cancellable);
             
-            return new Imap.Folder(session_mgr, status, mailbox_info);
+            folder = new Imap.Folder(session_mgr, status, mailbox_info);
         } else {
-            return new Imap.Folder.unselectable(session_mgr, mailbox_info);
+            folder = new Imap.Folder.unselectable(session_mgr, mailbox_info);
         }
+        
+        folders.set(path, folder);
+        
+        return folder;
     }
     
     private async StatusData fetch_status_async(FolderPath path, Cancellable? cancellable)
@@ -211,8 +223,18 @@ private class Geary.Imap.Account : BaseObject {
         Gee.Map<StatusCommand, MailboxSpecifier> cmd_map = new Gee.HashMap<
             StatusCommand, MailboxSpecifier>();
         foreach (MailboxInformation mailbox_info in child_info) {
+            // if already have an Imap.Folder for this mailbox, use that
+            if (folders.has_key(mailbox_info.path)) {
+                child_folders.add(folders.get(mailbox_info.path));
+                
+                continue;
+            }
+            
+            // if new mailbox is unselectable, don't bother doing a STATUS command
             if (mailbox_info.attrs.contains(MailboxAttribute.NO_SELECT)) {
-                child_folders.add(new Imap.Folder.unselectable(session_mgr, mailbox_info));
+                Imap.Folder folder = new Imap.Folder.unselectable(session_mgr, mailbox_info);
+                folders.set(folder.path, folder);
+                child_folders.add(folder);
                 
                 continue;
             }
@@ -255,7 +277,10 @@ private class Geary.Imap.Account : BaseObject {
             }
             
             status_results.remove(found_status);
-            child_folders.add(new Imap.Folder(session_mgr, found_status, mailbox_info));
+            
+            Imap.Folder folder = new Imap.Folder(session_mgr, found_status, mailbox_info);
+            folders.set(folder.path, folder);
+            child_folders.add(folder);
         }
         
         if (status_results.size > 0)
@@ -307,10 +332,8 @@ private class Geary.Imap.Account : BaseObject {
         // stash all MailboxInformation by path
         // TODO: remove any MailboxInformation for this parent that is not found (i.e. has been
         // removed on the server)
-        foreach (MailboxInformation mailbox_info in list_results) {
-            FolderPath path = mailbox_info.mailbox.to_folder_path(mailbox_info.delim);
-            path_to_mailbox.set(path, mailbox_info);
-        }
+        foreach (MailboxInformation mailbox_info in list_results)
+            path_to_mailbox.set(mailbox_info.path, mailbox_info);
         
         return (list_results.size > 0) ? list_results : null;
     }
diff --git a/src/engine/imap/api/imap-folder.vala b/src/engine/imap/api/imap-folder.vala
index 87eae1d..a38dc75 100644
--- a/src/engine/imap/api/imap-folder.vala
+++ b/src/engine/imap/api/imap-folder.vala
@@ -65,7 +65,7 @@ private class Geary.Imap.Folder : BaseObject {
         
         this.session_mgr = session_mgr;
         this.info = info;
-        path = info.mailbox.to_folder_path(info.delim);
+        path = info.path;
         
         properties = new Imap.FolderProperties.status(status, info.attrs);
     }
@@ -73,7 +73,7 @@ private class Geary.Imap.Folder : BaseObject {
     internal Folder.unselectable(ClientSessionManager session_mgr, MailboxInformation info) {
         this.session_mgr = session_mgr;
         this.info = info;
-        path = info.mailbox.to_folder_path(info.delim);
+        path = info.path;
         
         properties = new Imap.FolderProperties(0, 0, 0, null, null, info.attrs);
     }
diff --git a/src/engine/imap/response/imap-mailbox-information.vala 
b/src/engine/imap/response/imap-mailbox-information.vala
index 1e6520b..f29294e 100644
--- a/src/engine/imap/response/imap-mailbox-information.vala
+++ b/src/engine/imap/response/imap-mailbox-information.vala
@@ -30,10 +30,19 @@ public class Geary.Imap.MailboxInformation : Object {
      */
     public MailboxAttributes attrs { get; private set; }
     
+    /**
+     * The { link Geary.FolderPath} for the mailbox.
+     *
+     * This is constructed from the supplied { link mailbox} and { link delim} returned from the
+     * server.
+     */
+    public Geary.FolderPath path { get; private set; }
+    
     public MailboxInformation(MailboxSpecifier mailbox, string? delim, MailboxAttributes attrs) {
         this.mailbox = mailbox;
         this.delim = delim;
         this.attrs = attrs;
+        path = mailbox.to_folder_path(delim);
     }
     
     /**
diff --git a/src/engine/imap/transport/imap-client-connection.vala 
b/src/engine/imap/transport/imap-client-connection.vala
index 433406c..6b91369 100644
--- a/src/engine/imap/transport/imap-client-connection.vala
+++ b/src/engine/imap/transport/imap-client-connection.vala
@@ -30,7 +30,7 @@ public class Geary.Imap.ClientConnection : BaseObject {
      */
     public const uint DEFAULT_COMMAND_TIMEOUT_SEC = 15;
     
-    private const int FLUSH_TIMEOUT_MSEC = 100;
+    private const int FLUSH_TIMEOUT_MSEC = 10;
     
     private enum State {
         UNCONNECTED,


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