[geary/wip/714104-refine-account-dialog: 45/69] Get basic new account verification working.



commit 468a7a674b42f2c87afe70d1ca7184582bceb363
Author: Michael Gratton <mike vee net>
Date:   Fri Sep 7 22:49:10 2018 +1000

    Get basic new account verification working.

 src/client/accounts/accounts-editor-add-pane.vala | 137 +++++++++++++++++-----
 src/client/accounts/goa-service-information.vala  |  49 +++++++-
 src/engine/api/geary-engine.vala                  |  56 +++++----
 ui/accounts_editor_add_pane.ui                    | 129 ++++++++++----------
 4 files changed, 259 insertions(+), 112 deletions(-)
---
diff --git a/src/client/accounts/accounts-editor-add-pane.vala 
b/src/client/accounts/accounts-editor-add-pane.vala
index 4b4c72f3..405d26ca 100644
--- a/src/client/accounts/accounts-editor-add-pane.vala
+++ b/src/client/accounts/accounts-editor-add-pane.vala
@@ -50,15 +50,20 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
     [GtkChild]
     private Gtk.Button create_button;
 
+    [GtkChild]
+    private Gtk.Spinner create_spinner;
+
     private NameRow real_name;
     private EmailRow email = new EmailRow();
     private string last_valid_email = "";
 
     private HostnameRow imap_hostname = new HostnameRow(Geary.Protocol.IMAP);
+    private TransportSecurityRow imap_tls = new TransportSecurityRow();
     private LoginRow imap_login = new LoginRow();
     private PasswordRow imap_password = new PasswordRow();
 
     private HostnameRow smtp_hostname = new HostnameRow(Geary.Protocol.SMTP);
+    private TransportSecurityRow smtp_tls = new TransportSecurityRow();
     private SmtpAuthRow smtp_auth = new SmtpAuthRow();
     private LoginRow smtp_login = new LoginRow();
     private PasswordRow smtp_password = new PasswordRow();
@@ -101,20 +106,24 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
         this.email.value.changed.connect(on_email_changed);
 
         this.imap_hostname.validator.notify["state"].connect(on_validated);
+        this.imap_tls.hide();
         this.imap_login.validator.notify["state"].connect(on_validated);
         this.imap_password.validator.notify["state"].connect(on_validated);
 
         this.smtp_hostname.validator.notify["state"].connect(on_validated);
+        this.smtp_tls.hide();
         this.smtp_auth.value.changed.connect(on_smtp_auth_changed);
         this.smtp_login.validator.notify["state"].connect(on_validated);
         this.smtp_password.validator.notify["state"].connect(on_validated);
 
         if (provider == Geary.ServiceProvider.OTHER) {
             this.receiving_list.add(this.imap_hostname);
+            this.receiving_list.add(this.imap_tls);
             this.receiving_list.add(this.imap_login);
             this.receiving_list.add(this.imap_password);
 
             this.sending_list.add(this.smtp_hostname);
+            this.sending_list.add(this.smtp_tls);
             this.sending_list.add(this.smtp_auth);
         } else {
             this.details_list.add(this.imap_password);
@@ -139,10 +148,15 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
     }
 
     private async void validate_account(GLib.Cancellable? cancellable) {
+        this.create_spinner.show();
+        this.create_spinner.start();
         this.create_button.set_sensitive(false);
         this.set_sensitive(false);
 
         bool is_valid = false;
+        string message = "";
+        Gtk.Widget? to_focus = null;
+
         Geary.ServiceInformation imap = new_imap_service();
         Geary.ServiceInformation smtp = new_smtp_service();
 
@@ -162,55 +176,92 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
             try {
                 yield this.engine.validate_imap(account, cancellable);
                 imap_valid = true;
+            } catch (Geary.ImapError.UNAUTHENTICATED err) {
+                debug("Error authenticating IMAP service: %s", err.message);
+                to_focus = this.imap_login.value;
+                // Translators: In-app notification label
+                message = _("Check your receiving login and password");
             } catch (GLib.Error err) {
                 debug("Error validating IMAP service: %s", err.message);
-                // XXX do something with this
+                this.imap_tls.show();
+                to_focus = this.imap_hostname.value;
+                // Translators: In-app notification label
+                message = _("Check your receiving server details");
             }
 
-            // Only validate SMTP if not using IMAP creds, or if the
-            // IMAP creds are good, so we don't check known bad creds
-            if (!smtp.smtp_use_imap_credentials || imap_valid) {
-                //notification.label = _("Checking sending server…");
-
+            if (imap_valid) {
+                debug("Validating SMTP...");
                 try {
                     yield this.engine.validate_smtp(account, cancellable);
                     smtp_valid = true;
+                } catch (Geary.SmtpError.AUTHENTICATION_FAILED err) {
+                    debug("Error authenticating SMTP service: %s", err.message);
+                    // There was an SMTP auth error, but IMAP already
+                    // succeeded, so the user probably needs to
+                    // specify custom creds here
+                    this.smtp_auth.source = Geary.SmtpCredentials.CUSTOM;
+                    to_focus = this.smtp_login.value;
+                    // Translators: In-app notification label
+                    message = _("Check your sending login and password");
                 } catch (GLib.Error err) {
                     debug("Error validating SMTP service: %s", err.message);
-                    // XXX do something with this
+                    this.smtp_tls.show();
+                    to_focus = this.smtp_hostname.value;
+                    // Translators: In-app notification label
+                    message = _("Check your sending server details");
                 }
             }
 
             is_valid = imap_valid && smtp_valid;
         } else {
-            //notification.label = _("Checking account…");
             try {
                 yield this.engine.validate_imap(account, cancellable);
                 is_valid = true;
+            } catch (Geary.ImapError.UNAUTHENTICATED err) {
+                debug("Error authenticating provider: %s", err.message);
+                to_focus = this.email.value;
+                // Translators: In-app notification label
+                message = _("Check your email address and password");
             } catch (GLib.Error err) {
-                debug("Error validating provider IMAP: %s", err.message);
-                // XXX do something with this
+                debug("Error validating provider service: %s", err.message);
+                is_valid = false;
+                // Translators: In-app notification label
+                message = _("Could not connect, check your network");
             }
         }
 
         if (is_valid) {
             try {
                 yield this.accounts.create_account(account, cancellable);
+                this.editor.pop();
             } catch (GLib.Error err) {
                 debug("Failed to create new local account: %s", err.message);
-                // XXX do something with this
+                is_valid = false;
+                // Translators: In-app notification label for a
+                // generic error creating an account
+                message = _("An unexpected problem occurred");
+            }
+        }
+
+        this.create_spinner.stop();
+        this.create_spinner.hide();
+        this.create_button.set_sensitive(true);
+        this.set_sensitive(true);
+
+        // Focus and pop up the notification after re-sensitising
+        // so it actually succeeds.
+        if (!is_valid) {
+            if (to_focus != null) {
+                to_focus.grab_focus();
             }
-            this.editor.pop();
-        } else {
             add_notification(
                 new InAppNotification(
-                    _("Account not added, check the details below")
+                    // Translators: In-app notification label, the
+                    // string substitution is a more detailed reason.
+                    _("Account not created: %s").printf(message)
                 )
             );
         }
-
-        this.create_button.set_sensitive(true);
-        this.set_sensitive(true);
     }
 
     private LocalServiceInformation new_imap_service() {
@@ -230,6 +281,10 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
             GLib.NetworkAddress address = host.validated_address;
             service.host = address.hostname;
             service.port = (uint16) address.port;
+
+            Geary.TlsNegotiationMethod tls = this.imap_tls.value.method;
+            service.use_ssl = (tls == Geary.TlsNegotiationMethod.TRANSPORT);
+            service.use_starttls = (tls == Geary.TlsNegotiationMethod.START_TLS);
         } else {
             this.provider.setup_service(service);
             service.credentials = new Geary.Credentials(
@@ -247,7 +302,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
            this.accounts.new_libsecret_service(Geary.Protocol.SMTP);
 
         if (this.provider == Geary.ServiceProvider.OTHER) {
-            switch (this.smtp_auth.get_value()) {
+            switch (this.smtp_auth.source) {
             case Geary.SmtpCredentials.NONE:
                 service.smtp_noauth = true;
                 service.smtp_use_imap_credentials = false;
@@ -276,6 +331,13 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
 
             service.host = address.hostname;
             service.port = (uint16) address.port;
+
+            Geary.TlsNegotiationMethod tls = this.smtp_tls.value.method;
+            service.use_ssl = (tls == Geary.TlsNegotiationMethod.TRANSPORT);
+            service.use_starttls = (tls == Geary.TlsNegotiationMethod.START_TLS);
+
+            debug("SMTP service: TLS: %s, STARTTLS: %s",
+                  service.use_ssl.to_string(), service.use_starttls.to_string());
         } else {
             this.provider.setup_service(service);
         }
@@ -319,7 +381,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
     }
 
     private void on_smtp_auth_changed() {
-        if (this.smtp_auth.get_value() == Geary.SmtpCredentials.CUSTOM) {
+        if (this.smtp_auth.source == Geary.SmtpCredentials.CUSTOM) {
             this.sending_list.add(this.smtp_login);
             this.sending_list.add(this.smtp_password);
         } else if (this.smtp_login.parent != null) {
@@ -346,6 +408,7 @@ internal class Accounts.EditorAddPane : Gtk.Grid, EditorPane {
         Gtk.Container? next = null;
         if (direction == Gtk.DirectionType.DOWN) {
             if (widget == this.details_list) {
+                debug("Have details!");
                 next = this.receiving_list;
             } else if (widget == this.receiving_list) {
                 next = this.sending_list;
@@ -512,9 +575,35 @@ private class Accounts.HostnameRow : EntryRow {
 }
 
 
+private class Accounts.TransportSecurityRow :
+    LabelledEditorRow<EditorAddPane,TlsComboBox> {
+
+    public TransportSecurityRow() {
+        TlsComboBox value = new TlsComboBox();
+        base(value.label, value);
+        // Set to Transport TLS by default per RFC 8314
+        this.value.method = Geary.TlsNegotiationMethod.TRANSPORT;
+    }
+
+}
+
+
 private class Accounts.SmtpAuthRow :
     LabelledEditorRow<EditorAddPane,Gtk.ComboBoxText> {
 
+    public Geary.SmtpCredentials source {
+        get {
+            try {
+                return Geary.SmtpCredentials.for_value(this.value.active_id);
+            } catch {
+                return Geary.SmtpCredentials.IMAP;
+            }
+        }
+        set {
+            this.value.active_id = value.to_value();
+        }
+    }
+
 
     public SmtpAuthRow() {
         base(
@@ -530,15 +619,7 @@ private class Accounts.SmtpAuthRow :
         this.value.append(Geary.SmtpCredentials.IMAP.to_value(), _("Use IMAP login"));
         this.value.append(Geary.SmtpCredentials.CUSTOM.to_value(), _("Use different login"));
 
-        this.value.active_id = Geary.SmtpCredentials.IMAP.to_value();
-    }
-
-    public Geary.SmtpCredentials get_value() {
-        try {
-            return Geary.SmtpCredentials.for_value(this.value.active_id);
-        } catch {
-            return Geary.SmtpCredentials.IMAP;
-        }
+        this.source = Geary.SmtpCredentials.IMAP;
     }
 
 }
diff --git a/src/client/accounts/goa-service-information.vala 
b/src/client/accounts/goa-service-information.vala
index 89b83ac6..d976a0c6 100644
--- a/src/client/accounts/goa-service-information.vala
+++ b/src/client/accounts/goa-service-information.vala
@@ -25,10 +25,16 @@ public class GoaServiceInformation : Geary.ServiceInformation {
         if (mail != null) {
             switch (this.protocol) {
             case Geary.Protocol.IMAP:
-                this.host = mail.imap_host;
-                this.port = Geary.Imap.ClientConnection.IMAP_TLS_PORT;
+                parse_host_name(mail.imap_host);
                 this.use_ssl = mail.imap_use_ssl;
                 this.use_starttls = mail.imap_use_tls;
+
+                if (this.port == 0) {
+                    this.port = this.use_ssl
+                        ? Geary.Imap.ClientConnection.IMAP_TLS_PORT
+                        : Geary.Imap.ClientConnection.IMAP_PORT;
+                }
+
                 this.credentials = new Geary.Credentials(
                     ((GoaMediator) this.mediator).method,
                     mail.imap_user_name
@@ -36,12 +42,22 @@ public class GoaServiceInformation : Geary.ServiceInformation {
                 break;
 
             case Geary.Protocol.SMTP:
-                this.host = mail.smtp_host;
-                this.port = Geary.Smtp.ClientConnection.SUBMISSION_TLS_PORT;
+                parse_host_name(mail.smtp_host);
                 this.use_ssl = mail.smtp_use_ssl;
                 this.use_starttls = mail.smtp_use_tls;
                 this.smtp_noauth = !(mail.smtp_use_auth);
                 this.smtp_use_imap_credentials = false;
+
+                if (this.port == 0) {
+                    if (this.use_ssl) {
+                        this.port = Geary.Smtp.ClientConnection.SUBMISSION_TLS_PORT;
+                    } else if (this.smtp_noauth) {
+                        this.port = Geary.Smtp.ClientConnection.SMTP_PORT;
+                    } else {
+                        this.port = Geary.Smtp.ClientConnection.SUBMISSION_PORT;
+                    }
+                }
+
                 if (!this.smtp_noauth) {
                     this.credentials = new Geary.Credentials(
                         ((GoaMediator) this.mediator).method,
@@ -61,4 +77,29 @@ public class GoaServiceInformation : Geary.ServiceInformation {
         return copy;
     }
 
+    private void parse_host_name(string host_name) {
+        // Fall back to trying to use the host name as-is.
+        // At least the user can see it in the settings if
+        // they look.
+        this.host = host_name;
+        this.port = 0;
+
+        try {
+            GLib.NetworkAddress address = GLib.NetworkAddress.parse(
+                host_name, this.port
+            );
+
+            this.host = address.hostname;
+            this.port = (uint16) address.port;
+        } catch (GLib.Error err) {
+            warning(
+                "GOA account \"%s\" %s hostname \"%s\": %",
+                this.account.get_account().id,
+                this.protocol.to_value(),
+                host_name,
+                err.message
+            );
+        }
+    }
+
 }
diff --git a/src/engine/api/geary-engine.vala b/src/engine/api/geary-engine.vala
index dbe1620a..b577e909 100644
--- a/src/engine/api/geary-engine.vala
+++ b/src/engine/api/geary-engine.vala
@@ -257,25 +257,33 @@ public class Geary.Engine : BaseObject {
         bool login_failed = false;
         imap_session.login_failed.connect(() => login_failed = true);
 
+        GLib.Error? login_err = null;
         try {
             yield imap_session.connect_async(cancellable);
             yield imap_session.initiate_session_async(
                 account.imap.credentials, cancellable
             );
-        } finally {
-            try {
-                yield imap_session.disconnect_async(cancellable);
-            } catch {
-                // Oh well
-            }
-            account.disconnect_service_endpoints();
-            account.untrusted_host.disconnect(on_untrusted_host);
+        } catch (GLib.Error err) {
+            login_err = err;
+        }
+
+        try {
+            yield imap_session.disconnect_async(cancellable);
+        } catch {
+            // Oh well
         }
 
+        account.disconnect_service_endpoints();
+        account.untrusted_host.disconnect(on_untrusted_host);
+
         if (login_failed) {
             // XXX This should be a LOGIN_FAILED error or something
             throw new ImapError.UNAUTHENTICATED("Login failed");
         }
+
+        if (login_err != null) {
+            throw login_err;
+        }
     }
 
     /**
@@ -289,12 +297,10 @@ public class Geary.Engine : BaseObject {
         if (account.smtp.port == 0) {
             if (account.smtp.use_ssl) {
                 account.smtp.port = Smtp.ClientConnection.SUBMISSION_TLS_PORT;
-            } else if (account.smtp.use_starttls) {
-                account.smtp.port = account.smtp.smtp_noauth
-                    ? Smtp.ClientConnection.SMTP_PORT
-                    : Smtp.ClientConnection.SUBMISSION_PORT;
-            } else {
+            } else if (account.smtp.smtp_noauth) {
                 account.smtp.port = Smtp.ClientConnection.SMTP_PORT;
+            } else {
+                account.smtp.port = Smtp.ClientConnection.SUBMISSION_PORT;
             }
         }
 
@@ -307,18 +313,26 @@ public class Geary.Engine : BaseObject {
             account.smtp.endpoint
         );
 
+        GLib.Error? login_err = null;
         try {
             yield smtp_session.login_async(
                 account.get_smtp_credentials(), cancellable
             );
-        } finally {
-            try {
-                yield smtp_session.logout_async(true, cancellable);
-            } catch {
-                // Oh well
-            }
-            account.disconnect_service_endpoints();
-            account.untrusted_host.disconnect(on_untrusted_host);
+        } catch (GLib.Error err) {
+            login_err = err;
+        }
+
+        try {
+            yield smtp_session.logout_async(true, cancellable);
+        } catch {
+            // Oh well
+        }
+
+        account.disconnect_service_endpoints();
+        account.untrusted_host.disconnect(on_untrusted_host);
+
+        if (login_err != null) {
+            throw login_err;
         }
     }
 
diff --git a/ui/accounts_editor_add_pane.ui b/ui/accounts_editor_add_pane.ui
index 1b27d776..03d0e4d7 100644
--- a/ui/accounts_editor_add_pane.ui
+++ b/ui/accounts_editor_add_pane.ui
@@ -2,6 +2,76 @@
 <!-- Generated with glade 3.22.1 -->
 <interface>
   <requires lib="gtk+" version="3.20"/>
+  <object class="GtkHeaderBar" id="header">
+    <property name="visible">True</property>
+    <property name="can_focus">False</property>
+    <property name="title">Add an account</property>
+    <property name="has_subtitle">False</property>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <child>
+          <object class="GtkButton" id="back_button">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_back_button_clicked" swapped="no"/>
+            <child>
+              <object class="GtkImage">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="no_show_all">True</property>
+                <property name="icon_name">go-previous-symbolic</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <child>
+      <object class="GtkGrid">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="column_spacing">12</property>
+        <child>
+          <object class="GtkSpinner" id="create_spinner">
+            <property name="visible">True</property>
+            <property name="can_focus">False</property>
+          </object>
+          <packing>
+            <property name="left_attach">0</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkButton" id="create_button">
+            <property name="label" translatable="yes">Create</property>
+            <property name="visible">True</property>
+            <property name="sensitive">False</property>
+            <property name="can_focus">True</property>
+            <property name="receives_default">True</property>
+            <signal name="clicked" handler="on_create_button_clicked" swapped="no"/>
+            <style>
+              <class name="suggested-action"/>
+            </style>
+          </object>
+          <packing>
+            <property name="left_attach">1</property>
+            <property name="top_attach">0</property>
+          </packing>
+        </child>
+      </object>
+      <packing>
+        <property name="pack_type">end</property>
+        <property name="position">1</property>
+      </packing>
+    </child>
+  </object>
   <object class="GtkAdjustment" id="pane_adjustment">
     <property name="upper">100</property>
     <property name="step_increment">1</property>
@@ -170,63 +240,4 @@
       <class name="geary-accounts-editor-pane"/>
     </style>
   </template>
-  <object class="GtkHeaderBar" id="header">
-    <property name="visible">True</property>
-    <property name="can_focus">False</property>
-    <property name="title">Add an account</property>
-    <property name="has_subtitle">False</property>
-    <child>
-      <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <child>
-          <object class="GtkButton" id="back_button">
-            <property name="visible">True</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <signal name="clicked" handler="on_back_button_clicked" swapped="no"/>
-            <child>
-              <object class="GtkImage">
-                <property name="visible">True</property>
-                <property name="can_focus">False</property>
-                <property name="no_show_all">True</property>
-                <property name="icon_name">go-previous-symbolic</property>
-              </object>
-            </child>
-          </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
-          </packing>
-        </child>
-      </object>
-    </child>
-    <child>
-      <object class="GtkGrid">
-        <property name="visible">True</property>
-        <property name="can_focus">False</property>
-        <child>
-          <object class="GtkButton" id="create_button">
-            <property name="label" translatable="yes">Create</property>
-            <property name="visible">True</property>
-            <property name="sensitive">False</property>
-            <property name="can_focus">True</property>
-            <property name="receives_default">True</property>
-            <signal name="clicked" handler="on_create_button_clicked" swapped="no"/>
-            <style>
-              <class name="suggested-action"/>
-            </style>
-          </object>
-          <packing>
-            <property name="left_attach">0</property>
-            <property name="top_attach">0</property>
-          </packing>
-        </child>
-      </object>
-      <packing>
-        <property name="pack_type">end</property>
-        <property name="position">1</property>
-      </packing>
-    </child>
-  </object>
 </interface>


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