[geary] Show Inbox children on XLIST non-English accounts: Closes bug #729372



commit 711ae1ca83a98c87d83207b3022b06cec570fb23
Author: Jim Nelson <jim yorba org>
Date:   Wed May 14 18:35:35 2014 -0700

    Show Inbox children on XLIST non-English accounts: Closes bug #729372
    
    Although a prior fix dealt with the XLIST issue of a translated Inbox
    name ("Posteingang") not being available when directly addressing the
    folder ("INBOX"), it didn't deal with addressing children (subfolders)
    of Inbox ("Posteingang/eBay") -> "INBOX/eBay").  This ensures Geary
    can always identify Inbox, whether its translated name or INBOX.

 .../yahoo/imap-engine-yahoo-account.vala           |    2 +-
 src/engine/imap/api/imap-account.vala              |   78 ++++++++++++++++++--
 src/engine/imap/api/imap-folder.vala               |    8 +-
 .../imap/message/imap-mailbox-specifier.vala       |   21 +++++-
 .../imap/response/imap-mailbox-information.vala    |   28 ++++---
 src/engine/imap/response/imap-server-data.vala     |    2 +-
 6 files changed, 112 insertions(+), 27 deletions(-)
---
diff --git a/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala 
b/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
index 68958e5..01a3086 100644
--- a/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
+++ b/src/engine/imap-engine/yahoo/imap-engine-yahoo-account.vala
@@ -40,7 +40,7 @@ private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount {
         if (special_map == null) {
             special_map = new Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType>();
             
-            special_map.set(Imap.MailboxSpecifier.inbox.to_folder_path(), Geary.SpecialFolderType.INBOX);
+            special_map.set(Imap.MailboxSpecifier.inbox.to_folder_path(null, null), 
Geary.SpecialFolderType.INBOX);
             special_map.set(new Imap.FolderRoot("Sent", null), Geary.SpecialFolderType.SENT);
             special_map.set(new Imap.FolderRoot("Draft", null), Geary.SpecialFolderType.DRAFTS);
             special_map.set(new Imap.FolderRoot("Bulk Mail", null), Geary.SpecialFolderType.SPAM);
diff --git a/src/engine/imap/api/imap-account.vala b/src/engine/imap/api/imap-account.vala
index 0f4de58..6be8706 100644
--- a/src/engine/imap/api/imap-account.vala
+++ b/src/engine/imap/api/imap-account.vala
@@ -32,6 +32,8 @@ private class Geary.Imap.Account : BaseObject {
     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;
+    private Gee.List<ServerData>? server_data_collector = null;
+    private Imap.MailboxSpecifier? inbox_specifier = null;
     
     public signal void login_failed(Geary.Credentials cred);
     
@@ -89,7 +91,14 @@ private class Geary.Imap.Account : BaseObject {
                 
                 account_session.list.connect(on_list_data);
                 account_session.status.connect(on_status_data);
+                account_session.server_data_received.connect(on_server_data_received);
                 account_session.disconnected.connect(on_disconnected);
+                
+                // Learn the magic XLIST <translated Inbox name> -> Inbox mapping
+                if (account_session.capabilities.has_capability(Imap.Capabilities.XLIST))
+                    yield determine_xlist_inbox(account_session, cancellable);
+                else
+                    inbox_specifier = MailboxSpecifier.inbox;
             } catch (Error claim_err) {
                 err = claim_err;
             }
@@ -97,12 +106,52 @@ private class Geary.Imap.Account : BaseObject {
         
         account_session_mutex.release(ref token);
         
-        if (err != null)
+        if (err != null) {
+            if (account_session != null)
+                yield drop_session_async(null);
+            
             throw err;
+        }
         
         return account_session;
     }
     
+    private async void determine_xlist_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 cmd_mutex.claim_async(cancellable);
+        
+        // clear for now
+        inbox_specifier = null;
+        
+        // collect server data directly for direct decoding
+        server_data_collector = new Gee.ArrayList<ServerData>();
+        
+        Error? throw_err = null;
+        try {
+            Imap.StatusResponse response = yield session.send_command_async(
+                new ListCommand(MailboxSpecifier.inbox, true, null), cancellable);
+            if (response.status == Imap.Status.OK && server_data_collector.size > 0)
+                inbox_specifier = MailboxInformation.decode(server_data_collector[0], false).mailbox;
+        } catch (Error err) {
+            throw_err = err;
+        }
+        
+        server_data_collector = null;
+        
+        // fall back on standard name
+        if (inbox_specifier == null)
+            inbox_specifier = MailboxSpecifier.inbox;
+        
+        debug("[%s] INBOX specifier: %s", to_string(), inbox_specifier.to_string());
+        
+        cmd_mutex.release(ref token);
+        
+        if (throw_err != null)
+            throw throw_err;
+    }
+    
     private async void drop_session_async(Cancellable? cancellable) {
         int token;
         try {
@@ -122,6 +171,7 @@ private class Geary.Imap.Account : BaseObject {
             
             account_session.list.disconnect(on_list_data);
             account_session.status.disconnect(on_status_data);
+            account_session.server_data_received.disconnect(on_server_data_received);
             account_session.disconnected.disconnect(on_disconnected);
             
             account_session = null;
@@ -144,6 +194,11 @@ private class Geary.Imap.Account : BaseObject {
             status_collector.add(status_data);
     }
     
+    private void on_server_data_received(ServerData server_data) {
+        if (server_data_collector != null)
+            server_data_collector.add(server_data);
+    }
+    
     private void on_disconnected() {
         drop_session_async.begin(null);
     }
@@ -185,13 +240,16 @@ private class Geary.Imap.Account : BaseObject {
         if (mailbox_info == null)
             throw_not_found(path);
         
+        // construct folder path for new folder, converting XLIST Inbox name to canonical INBOX
+        FolderPath folder_path = mailbox_info.get_path(inbox_specifier);
+        
         Imap.Folder folder;
         if (!mailbox_info.attrs.is_no_select) {
             StatusData status = yield fetch_status_async(path, StatusDataType.all(), cancellable);
             
-            folder = new Imap.Folder(session_mgr, status, mailbox_info);
+            folder = new Imap.Folder(folder_path, session_mgr, status, mailbox_info);
         } else {
-            folder = new Imap.Folder.unselectable(session_mgr, mailbox_info);
+            folder = new Imap.Folder.unselectable(folder_path, session_mgr, mailbox_info);
         }
         
         folders.set(path, folder);
@@ -268,7 +326,8 @@ private class Geary.Imap.Account : BaseObject {
         foreach (MailboxInformation mailbox_info in child_info) {
             // if new mailbox is unselectable, don't bother doing a STATUS command
             if (mailbox_info.attrs.is_no_select) {
-                Imap.Folder folder = new Imap.Folder.unselectable(session_mgr, mailbox_info);
+                Imap.Folder folder = new Imap.Folder.unselectable(mailbox_info.get_path(inbox_specifier),
+                    session_mgr, mailbox_info);
                 folders.set(folder.path, folder);
                 child_folders.add(folder);
                 
@@ -318,12 +377,14 @@ private class Geary.Imap.Account : BaseObject {
             
             status_results.remove(found_status);
             
+            FolderPath folder_path = mailbox_info.get_path(inbox_specifier);
+            
             // if already have an Imap.Folder for this mailbox, use that
-            Imap.Folder? folder = folders.get(mailbox_info.path);
+            Imap.Folder? folder = folders.get(folder_path);
             if (folder != null) {
                 folder.properties.update_status(found_status);
             } else {
-                folder = new Imap.Folder(session_mgr, found_status, mailbox_info);
+                folder = new Imap.Folder(folder_path, session_mgr, found_status, mailbox_info);
                 folders.set(folder.path, folder);
             }
             
@@ -375,7 +436,8 @@ private class Geary.Imap.Account : BaseObject {
         if (parent != null) {
             Gee.Iterator<MailboxInformation> iter = list_results.iterator();
             while (iter.next()) {
-                FolderPath list_path = 
iter.get().mailbox.to_folder_path(parent.get_root().default_separator);
+                FolderPath list_path = iter.get().mailbox.to_folder_path(parent.get_root().default_separator,
+                    inbox_specifier);
                 if (list_path.equal_to(parent)) {
                     debug("Removing parent from LIST results: %s", list_path.to_string());
                     iter.remove();
@@ -387,7 +449,7 @@ private class Geary.Imap.Account : BaseObject {
         // 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)
-            path_to_mailbox.set(mailbox_info.path, mailbox_info);
+            path_to_mailbox.set(mailbox_info.get_path(inbox_specifier), 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 c3fe6d4..e853ae9 100644
--- a/src/engine/imap/api/imap-folder.vala
+++ b/src/engine/imap/api/imap-folder.vala
@@ -71,20 +71,20 @@ private class Geary.Imap.Folder : BaseObject {
      */
     public signal void disconnected(ClientSession.DisconnectReason reason);
     
-    internal Folder(ClientSessionManager session_mgr, StatusData status, MailboxInformation info) {
+    internal Folder(FolderPath path, ClientSessionManager session_mgr, StatusData status, MailboxInformation 
info) {
         assert(status.mailbox.equal_to(info.mailbox));
         
         this.session_mgr = session_mgr;
         this.info = info;
-        path = info.path;
+        this.path = path;
         
         properties = new Imap.FolderProperties.status(status, info.attrs);
     }
     
-    internal Folder.unselectable(ClientSessionManager session_mgr, MailboxInformation info) {
+    internal Folder.unselectable(FolderPath path, ClientSessionManager session_mgr, MailboxInformation info) 
{
         this.session_mgr = session_mgr;
         this.info = info;
-        path = info.path;
+        this.path = path;
         
         properties = new Imap.FolderProperties(0, 0, 0, null, null, info.attrs);
     }
diff --git a/src/engine/imap/message/imap-mailbox-specifier.vala 
b/src/engine/imap/message/imap-mailbox-specifier.vala
index d6a864a..333d101 100644
--- a/src/engine/imap/message/imap-mailbox-specifier.vala
+++ b/src/engine/imap/message/imap-mailbox-specifier.vala
@@ -133,9 +133,26 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
         return path;
     }
     
-    public FolderPath to_folder_path(string? delim = null) {
+    /**
+     * Converts the { link MailboxSpecifier} into a { link FolderPath}.
+     *
+     * If the inbox_specifier is supplied, if the root element matches it, the canonical Inbox
+     * name is used in its place.  This is useful for XLIST where that command returns a translated
+     * name but the standard IMAP name ("INBOX") must be used in addressing its children.
+     */
+    public FolderPath to_folder_path(string? delim, MailboxSpecifier? inbox_specifier) {
+        // convert path to list of elements
         Gee.List<string> list = to_list(delim);
-        FolderPath path = new Imap.FolderRoot(list[0], delim);
+        
+        // if root element is same as supplied inbox specifier, use canonical inbox name, otherwise
+        // keep
+        FolderPath path;
+        if (inbox_specifier != null && list[0] == inbox_specifier.name)
+            path = new Imap.FolderRoot(CANONICAL_INBOX_NAME, delim);
+        else
+            path = new Imap.FolderRoot(list[0], delim);
+        
+        // walk down rest of elements adding as we go
         for (int ctr = 1; ctr < list.size; ctr++)
             path = path.get_child(list[ctr]);
         
diff --git a/src/engine/imap/response/imap-mailbox-information.vala 
b/src/engine/imap/response/imap-mailbox-information.vala
index f29294e..741a958 100644
--- a/src/engine/imap/response/imap-mailbox-information.vala
+++ b/src/engine/imap/response/imap-mailbox-information.vala
@@ -30,30 +30,25 @@ 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);
     }
     
     /**
      * Decodes { link ServerData} into a MailboxInformation representation.
      *
+     * If canonical_inbox is true, the { link MailboxAttributes} are searched for the \Inbox flag.
+     * If found, { link MailboxSpecifier.CANONICAL_INBOX_NAME} is used rather than the one returned
+     * by the server.
+     *
      * The ServerData must be the response to a LIST or XLIST command.
      *
      * @see ListCommand
      * @see ServerData.get_list
      */
-    public static MailboxInformation decode(ServerData server_data) throws ImapError {
+    public static MailboxInformation decode(ServerData server_data, bool canonical_inbox) throws ImapError {
         StringParameter cmd = server_data.get_as_string(1);
         if (!cmd.equals_ci(ListCommand.NAME) && !cmd.equals_ci(ListCommand.XLIST_NAME))
             throw new ImapError.PARSE_ERROR("Not LIST or XLIST data: %s", server_data.to_string());
@@ -80,7 +75,7 @@ public class Geary.Imap.MailboxInformation : Object {
             server_data.get_as_string(4));
         
         // Set \Inbox to standard path
-        if (Geary.Imap.MailboxAttribute.SPECIAL_FOLDER_INBOX in attributes) {
+        if (canonical_inbox && Geary.Imap.MailboxAttribute.SPECIAL_FOLDER_INBOX in attributes) {
             return new MailboxInformation(MailboxSpecifier.inbox,
                 (delim != null) ? delim.nullable_value : null, attributes);
         } else {
@@ -88,5 +83,16 @@ public class Geary.Imap.MailboxInformation : Object {
                 (delim != null) ? delim.nullable_value : null, attributes);
         }
     }
+    
+    /**
+     * The { link Geary.FolderPath} for the { link mailbox}.
+     *
+     * This is constructed from the supplied { link mailbox} and { link delim} returned from the
+     * server.  If the mailbox is the same as the supplied inbox_specifier, a canonical name for
+     * the Inbox is returned.
+     */
+    public Geary.FolderPath get_path(MailboxSpecifier? inbox_specifier) {
+        return mailbox.to_folder_path(delim, inbox_specifier);
+    }
 }
 
diff --git a/src/engine/imap/response/imap-server-data.vala b/src/engine/imap/response/imap-server-data.vala
index ecb5060..81c3ef0 100644
--- a/src/engine/imap/response/imap-server-data.vala
+++ b/src/engine/imap/response/imap-server-data.vala
@@ -127,7 +127,7 @@ public class Geary.Imap.ServerData : ServerResponse {
         if (server_data_type != ServerDataType.LIST && server_data_type != ServerDataType.XLIST)
             throw new ImapError.INVALID("Not LIST/XLIST data: %s", to_string());
         
-        return MailboxInformation.decode(this);
+        return MailboxInformation.decode(this, true);
     }
     
     /**


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