[geary/wip/768422-namespace-support: 5/6] Initial IMAP inbox and namespace information in client session.



commit 97b0770730274ecf22e94342a041421eede69273
Author: Michael James Gratton <mike vee net>
Date:   Thu Nov 2 17:29:17 2017 +1100

    Initial IMAP inbox and namespace information in client session.
    
    Since each IMAP connection may have different notions of namespaces and
    mailbox hierarchy delimiters, inbox names, and so on, store this
    information per-connection rather than per-account.
    
    Don't remove uses of old inbox and delimiters yet, that will be next.
    
    * src/engine/imap/api/imap-account.vala (Account): Remove list_inbox()
      and its single call site.
    
    * src/engine/imap/transport/imap-client-session.vala (ClientSession): Add
      class attributes for storing namespace prefix and mailbox hierarchy
      delimiter information for the inbox and RFC 2342 namespaces. Add a
      namespace signal, fire it when a namespace server response is reached.
      (ClientSession::initiate_session_async): After logging in and enabling
      compression, obtain the mailbox info for the inbox and either namespace
      information if supported, or else attempt to guess what the default user
      mailbox is.

 src/engine/imap/api/imap-account.vala              |   76 +--------------
 src/engine/imap/transport/imap-client-session.vala |  109 +++++++++++++++++++-
 2 files changed, 107 insertions(+), 78 deletions(-)
---
diff --git a/src/engine/imap/api/imap-account.vala b/src/engine/imap/api/imap-account.vala
index 290af37..3613441 100644
--- a/src/engine/imap/api/imap-account.vala
+++ b/src/engine/imap/api/imap-account.vala
@@ -99,9 +99,6 @@ private class Geary.Imap.Account : BaseObject {
                 account_session.status.connect(on_status_data);
                 account_session.server_data_received.connect(on_server_data_received);
                 account_session.disconnected.connect(on_disconnected);
-
-                // Determine INBOX, hierarchy delimiter, special use flags, etc
-                yield list_inbox(account_session, cancellable);
             } catch (Error claim_err) {
                 err = claim_err;
             }
@@ -115,79 +112,8 @@ private class Geary.Imap.Account : BaseObject {
             
             throw err;
         }
-        
-        return account_session;
-    }
-
-    private async void list_inbox(ClientSession session, Cancellable? cancellable)
-        throws Error {
-        // Can't use send_command_async() directly because this is
-        // called by claim_session_async(), which is called by
-        // send_command_async().
-        int token = yield this.cmd_mutex.claim_async(cancellable);
-        Error? release_error = null;
-        try {
-            // collect server data directly for direct decoding
-            this.server_data_collector = new Gee.ArrayList<ServerData>();
-
-            MailboxInformation? info = null;
-            Imap.StatusResponse response = yield session.send_command_async(
-                new ListCommand(MailboxSpecifier.inbox, false, null), cancellable);
-            if (response.status == Imap.Status.OK && !this.server_data_collector.is_empty) {
-                info = MailboxInformation.decode(this.server_data_collector[0], false);
-            }
-
-            if (info != null) {
-                this.inbox_specifier = info.mailbox;
-                this.hierarchy_delimiter = info.delim;
-            }
 
-            if (this.hierarchy_delimiter == null) {
-                // LIST response didn't include a delim for the
-                // INBOX. Ideally we'd just use NAMESPACE instead (Bug
-                // 726866) but for now, just list folders alongside the
-                // INBOX.
-                this.server_data_collector = new Gee.ArrayList<ServerData>();
-                response = yield session.send_command_async(
-                    new ListCommand.wildcarded(
-                        "", new MailboxSpecifier("%"), false, null
-                    ),
-                    cancellable
-                );
-                if (response.status == Imap.Status.OK) {
-                    foreach (ServerData data in this.server_data_collector) {
-                        info = MailboxInformation.decode(data, false);
-                        this.hierarchy_delimiter = info.delim;
-                        if (this.hierarchy_delimiter != null) {
-                            break;
-                        }
-                    }
-                }
-            }
-
-            if (this.inbox_specifier == null) {
-                throw new ImapError.INVALID("Unable to find INBOX");
-            }
-            if (this.hierarchy_delimiter == null) {
-                throw new ImapError.INVALID("Unable to determine hierarchy delimiter");
-            }
-            debug("[%s] INBOX specifier: %s", to_string(),
-                  this.inbox_specifier.to_string());
-            debug("[%s] Hierarchy delimiter: %s", to_string(),
-                  this.hierarchy_delimiter);
-        } finally {
-            this.server_data_collector = new Gee.ArrayList<ServerData>();
-            try {
-                this.cmd_mutex.release(ref token);
-            } catch (Error e) {
-                // Vala 0.32.1 won't let you throw an exception from a
-                // finally block :(
-                release_error = e;
-            }
-        }
-        if (release_error != null) {
-            throw release_error;
-        }
+        return account_session;
     }
 
     private async void drop_session_async(Cancellable? cancellable) {
diff --git a/src/engine/imap/transport/imap-client-session.vala 
b/src/engine/imap/transport/imap-client-session.vala
index 35669a9..22e8d61 100644
--- a/src/engine/imap/transport/imap-client-session.vala
+++ b/src/engine/imap/transport/imap-client-session.vala
@@ -215,6 +215,20 @@ public class Geary.Imap.ClientSession : BaseObject {
     private Nonblocking.Semaphore? connect_waiter = null;
     private Error? connect_err = null;
 
+    // While the following should be server specific, there is a small
+    // chance they will differ between connections if the connections
+    // connect to different servers in a cluster, or if configuration
+    // changes between connections. We do assume however that once
+    // connected, this information will remain the same. This
+    // information becomes current only after initiate_session_async()
+    // has successfully completed.
+    private MailboxInformation? inbox = null;
+    private Gee.List<Namespace> personal_namespaces = new Gee.ArrayList<Namespace>();
+    private Gee.List<Namespace> user_namespaces = new Gee.ArrayList<Namespace>();
+    private Gee.List<Namespace> shared_namespaces = new Gee.ArrayList<Namespace>();
+    private Gee.Map<string,Namespace> namespaces = new Gee.HashMap<string,Namespace>();
+
+
     //
     // Connection state changes
     //
@@ -261,7 +275,9 @@ public class Geary.Imap.ClientSession : BaseObject {
     public signal void search(int64[] seq_or_uid);
     
     public signal void status(StatusData status_data);
-    
+
+    public signal void @namespace(NamespaceResponse namespace);
+
     /**
      * If the mailbox name is null it indicates the type of state change that has occurred
      * (authorized -> selected/examined or vice-versa).  If new_name is null readonly should be
@@ -799,8 +815,91 @@ public class Geary.Imap.ClientSession : BaseObject {
         } else {
             debug("[%s] No compression available", to_string());
         }
+
+        Gee.List<ServerData> server_data = new Gee.ArrayList<ServerData>();
+        ulong data_id = this.server_data_received.connect((data) => { server_data.add(data); });
+        try {
+            // Determine what this connection calls the inbox
+            Imap.StatusResponse response = yield send_command_async(
+                new ListCommand(MailboxSpecifier.inbox, false, null),
+                cancellable
+            );
+            if (response.status == Status.OK && !server_data.is_empty) {
+                this.inbox = server_data[0].get_list();
+                debug("[%s] Using as INBOX: %s", to_string(), this.inbox.to_string());
+            } else {
+                throw new ImapError.INVALID("Unable to find INBOX");
+            }
+
+            // Try to determine what the connection's namespaces are
+            server_data.clear();
+            if (caps.has_capability(Capabilities.NAMESPACE)) {
+                response = yield send_command_async(
+                    new NamespaceCommand(),
+                    cancellable
+                );
+                if (response.status == Status.OK && !server_data.is_empty) {
+                    NamespaceResponse ns = server_data[0].get_namespace();
+                    update_namespaces(ns.personal, this.personal_namespaces);
+                    update_namespaces(ns.user, this.user_namespaces);
+                    update_namespaces(ns.shared, this.shared_namespaces);
+                } else {
+                    debug("[%s] NAMESPACE command failed", to_string());
+                }
+            }
+            server_data.clear();
+            if (!this.personal_namespaces.is_empty) {
+                debug("[%s] Default personal namespace: %s", to_string(), 
this.personal_namespaces[0].to_string());
+            } else {
+                debug("[%s] Personal namespace not found, guessing it", to_string());
+                string? prefix = "";
+                string? delim = this.inbox.delim;
+                if (!this.inbox.attrs.contains(MailboxAttribute.NO_INFERIORS) &&
+                    this.inbox.delim == ".") {
+                    // We're probably on an ancient Cyrus install that
+                    // doesn't support NAMESPACE, so assume they go in the inbox
+                    prefix = this.inbox.mailbox.name + ".";
+                }
+
+                if (delim == null) {
+                    // We still don't know what the delim is, so fetch
+                    // it. In particular, uw-imap sends a null prefix
+                    // for the inbox.
+                    response = yield send_command_async(
+                        new ListCommand(new MailboxSpecifier(prefix), false, null),
+                        cancellable
+                    );
+                    if (response.status == Status.OK && !server_data.is_empty) {
+                        MailboxInformation list = server_data[0].get_list();
+                        delim = list.delim;
+                    } else {
+                        throw new ImapError.INVALID("Unable to determine personal namespace delimiter");
+                    }
+                }
+
+                this.personal_namespaces.add(new Namespace(prefix, delim));
+                debug("[%s] Personal namespace guessed as: %s",
+                      to_string(), this.personal_namespaces[0].to_string());
+            }
+        } finally {
+            disconnect(data_id);
+        }
     }
-    
+
+    private inline void update_namespaces(Namespace[]? response, Gee.List<Namespace> list) {
+        if (response != null) {
+            foreach (Namespace ns in response) {
+                list.add(ns);
+                string prefix = ns.prefix;
+                string? delim = ns.delim;
+                if (delim != null && prefix.has_suffix(delim)) {
+                    prefix = prefix.substring(0, prefix.length - delim.length);
+                }
+                this.namespaces.set(prefix, ns);
+            }
+        }
+    }
+
     private uint on_login(uint state, uint event, void *user, Object? object) {
         MachineParams params = (MachineParams) object;
         
@@ -1595,7 +1694,11 @@ public class Geary.Imap.ClientSession : BaseObject {
             case ServerDataType.SEARCH:
                 search(server_data.get_search());
             break;
-            
+
+            case ServerDataType.NAMESPACE:
+                namespace(server_data.get_namespace());
+            break;
+
             // TODO: LSUB
             case ServerDataType.LSUB:
             default:


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