[geary/wip/713006-better-error-reporting] Allow passing addtional information when reporting engine errors.



commit d5721443fb8c3248e1e2185a0ddd4a35767346cb
Author: Michael James Gratton <mike vee net>
Date:   Mon Nov 13 23:34:29 2017 +1100

    Allow passing addtional information when reporting engine errors.
    
    * src/engine/api/geary-problem-report.vala: New problem enum,
      ProblemReport class and AccountProblemReport and ServiceProblemReport
      subclasses that encapsulate error, account and service information when
      reporting problems.
    
    * src/engine/api/geary-account.vala (Account): Remove old Problem enum,
      make report_problem signal and related notify methods accept a
      ProblemReport instance instead. Reorganise protected methods a bit and
      update subclasses and signal handlers.
    
    * src/client/components/main-window-info-bar.vala (MainWindowInfoBar):
      Substantially rework to handle ProblemReport instances via
      for_problem() constructor.
    
    * src/engine/imap-db/outbox/smtp-outbox-folder.vala (SmtpOutboxFolder):
      Substantially rework error handling yet again to get some better
      ProblemReport instances being generated.

 po/POTFILES.in                                     |    1 +
 src/CMakeLists.txt                                 |    1 +
 src/client/application/geary-controller.vala       |  119 +++++-----
 src/client/components/main-window-info-bar.vala    |  253 ++++++++++++--------
 src/engine/api/geary-account.vala                  |  157 +++++++------
 src/engine/api/geary-problem-report.vala           |  175 ++++++++++++++
 src/engine/imap-db/outbox/smtp-outbox-folder.vala  |   77 ++++---
 .../imap-engine/imap-engine-generic-account.vala   |    8 +-
 src/engine/imap/api/imap-account.vala              |   37 ++--
 ui/main-window-info-bar.ui                         |    2 +-
 10 files changed, 548 insertions(+), 282 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 1969cba..0cf137b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -129,6 +129,7 @@ src/engine/api/geary-folder.vala
 src/engine/api/geary-logging.vala
 src/engine/api/geary-named-flag.vala
 src/engine/api/geary-named-flags.vala
+src/engine/api/geary-problem-report.vala
 src/engine/api/geary-progress-monitor.vala
 src/engine/api/geary-revokable.vala
 src/engine/api/geary-search-folder.vala
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index efe2403..0f2a2e5 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -37,6 +37,7 @@ engine/api/geary-folder-supports-remove.vala
 engine/api/geary-logging.vala
 engine/api/geary-named-flag.vala
 engine/api/geary-named-flags.vala
+engine/api/geary-problem-report.vala
 engine/api/geary-progress-monitor.vala
 engine/api/geary-revokable.vala
 engine/api/geary-search-folder.vala
diff --git a/src/client/application/geary-controller.vala b/src/client/application/geary-controller.vala
index 55954bd..2af6674 100644
--- a/src/client/application/geary-controller.vala
+++ b/src/client/application/geary-controller.vala
@@ -873,71 +873,68 @@ public class GearyController : Geary.BaseObject {
         }
     }
 
-    private void report_problem(Geary.Account.Problem problem, Geary.Account account, Error? err) {
-        debug("Reported problem: %s Error: %s", problem.to_string(), err != null ? err.message : "(N/A)");
-
-        switch (problem) {
-        case Geary.Account.Problem.DATABASE_FAILURE:
-        case Geary.Account.Problem.HOST_UNREACHABLE:
-        case Geary.Account.Problem.NETWORK_UNAVAILABLE:
-        case Geary.Account.Problem.RECV_EMAIL_ERROR:
-        case Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED:
-        case Geary.Account.Problem.SEND_EMAIL_ERROR:
-        case Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED:
-            MainWindowInfoBar info_bar = new MainWindowInfoBar.for_problem(
-                problem, account, err
-            );
-            info_bar.retry.connect(on_retry_problem);
-            this.main_window.show_infobar(info_bar);
-            break;
-
-        case Geary.Account.Problem.SEND_EMAIL_SAVE_FAILED:
-            handle_outbox_failure(StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED);
-            break;
+    private void report_problem(Geary.ProblemReport report) {
+        debug("Problem reported: %s", report.to_string());
 
-        default:
-            assert_not_reached();
+        if (!(report.error is IOError.CANCELLED)) {
+            if (report.problem_type == Geary.ProblemType.SEND_EMAIL_SAVE_FAILED) {
+                handle_outbox_failure(StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED);
+            } else {
+                MainWindowInfoBar info_bar = new MainWindowInfoBar.for_problem(report);
+                info_bar.retry.connect(on_retry_problem);
+                this.main_window.show_infobar(info_bar);
+            }
         }
     }
 
     private void on_retry_problem(MainWindowInfoBar info_bar) {
-        switch (info_bar.problem) {
-        case Geary.Account.Problem.RECV_EMAIL_ERROR:
-        case Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED:
-            info_bar.account.start_incoming_client.begin((obj, ret) => {
-                    try {
-                        info_bar.account.start_incoming_client.end(ret);
-                    } catch (Error err) {
-                        report_problem(
-                            Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED,
-                            info_bar.account,
-                            err
-                        );
-                    }
-                });
-            break;
+        Geary.ServiceProblemReport? service_report =
+            info_bar.report as Geary.ServiceProblemReport;
+        Error retry_err = null;
+        if (service_report != null) {
+            Geary.Account? account = null;
+            try {
+                account = this.application.engine.get_account_instance(
+                    service_report.account
+                );
+            } catch (Error err) {
+                debug("Error getting account for error retry: %s", err.message);
+            }
 
-        case Geary.Account.Problem.SEND_EMAIL_ERROR:
-        case Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED:
-            info_bar.account.start_outgoing_client.begin((obj, ret) => {
-                    try {
-                        info_bar.account.start_outgoing_client.end(ret);
-                    } catch (Error err) {
-                        report_problem(
-                            Geary.Account.Problem.SEND_EMAIL_ERROR,
-                            info_bar.account,
-                            err
-                        );
-                    }
-                });
-            break;
+            if (account != null && account.is_open()) {
+                switch (service_report.service_type) {
+                case Geary.Service.IMAP:
+                    account.start_incoming_client.begin((obj, ret) => {
+                            try {
+                                account.start_incoming_client.end(ret);
+                            } catch (Error err) {
+                                retry_err = err;
+                            }
+                        });
+                    break;
 
-        default:
-            debug("Un-handled problem retry for %s: %s".printf(
-                      info_bar.account.information.id,
-                      info_bar.problem.to_string()
-                  ));
-            break;
+                case Geary.Service.SMTP:
+                    account.start_outgoing_client.begin((obj, ret) => {
+                            try {
+                                account.start_outgoing_client.end(ret);
+                            } catch (Error err) {
+                                retry_err = err;
+                            }
+                        });
+                    break;
+                }
+
+                if (retry_err != null) {
+                    report_problem(
+                        new Geary.ServiceProblemReport(
+                            Geary.ProblemType.GENERIC_ERROR,
+                            service_report.account,
+                            service_report.service_type,
+                            retry_err
+                        )
+                    );
+                }
+            }
         }
     }
 
@@ -984,8 +981,8 @@ public class GearyController : Geary.BaseObject {
         }
     }
 
-    private void on_report_problem(Geary.Account account, Geary.Account.Problem problem, Error? err) {
-        report_problem(problem, account, err);
+    private void on_report_problem(Geary.Account account, Geary.ProblemReport problem) {
+        report_problem(problem);
     }
 
     private void on_account_email_removed(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> ids) {
diff --git a/src/client/components/main-window-info-bar.vala b/src/client/components/main-window-info-bar.vala
index 6c3579f..13048f9 100644
--- a/src/client/components/main-window-info-bar.vala
+++ b/src/client/components/main-window-info-bar.vala
@@ -14,16 +14,8 @@ public class MainWindowInfoBar : Gtk.InfoBar {
 
     private enum ResponseType { COPY, DETAILS, RETRY; }
 
-
-    /** If reporting a problem returns, the specific problem else null. */
-    public Geary.Account.Problem? problem { get; private set; default = null; }
-
-    /** If reporting a problem for an account, returns the account else null. */
-    public Geary.Account? account { get; private set; default = null; }
-
-    /** If reporting a problem, returns the error thrown, if any. */
-    public Error error { get; private set; default = null; }
-
+    /** If reporting a problem, returns the problem report else null. */
+    public Geary.ProblemReport? report { get; private set; default = null; }
 
     /** Emitted when the user clicks the Retry button, if any. */
     public signal void retry();
@@ -42,75 +34,130 @@ public class MainWindowInfoBar : Gtk.InfoBar {
     private Gtk.TextView detail_text;
 
 
-    public MainWindowInfoBar.for_problem(Geary.Account.Problem problem,
-                                         Geary.Account account,
-                                         GLib.Error? error) {
-        string name = account.information.display_name;
+    public MainWindowInfoBar.for_problem(Geary.ProblemReport report) {
         Gtk.MessageType type = Gtk.MessageType.WARNING;
         string title = "";
         string descr = "";
         string? retry = null;
+        bool show_generic = false;
         bool show_close = false;
-        switch (problem) {
-        case Geary.Account.Problem.DATABASE_FAILURE:
-            type = Gtk.MessageType.ERROR;
-            title = _("A database problem has occurred");
-            descr = _("Messages for %s must be downloaded again.").printf(name);
-            show_close = true;
-            break;
 
-        case Geary.Account.Problem.HOST_UNREACHABLE:
-            // XXX should really be displaying the server name here
-            title = _("Could not contact server");
-            descr = _("Please check %s server names are correct and are working.").printf(name);
-            show_close = true;
-            break;
+        if (report is Geary.ServiceProblemReport) {
+            Geary.ServiceProblemReport service_report = (Geary.ServiceProblemReport) report;
+            Geary.Endpoint endpoint = service_report.endpoint;
+            string account = service_report.account.display_name;
+            string server = endpoint.remote_address.hostname;
+
+            if (report.problem_type == Geary.ProblemType.CONNECTION_ERROR &&
+                service_report.service_type == Geary.Service.IMAP) {
+                // Translators: String substitution is the account name
+                title = _("Problem connecting to incoming server for %s".printf(account));
+                // Translators: String substitution is the server name
+                descr = _("Could not connect to %s, check your Internet access and the server name and try 
again").printf(server);
+                retry = _("Retry connecting now");
+
+            } else if (report.problem_type == Geary.ProblemType.CONNECTION_ERROR &&
+                       service_report.service_type == Geary.Service.SMTP) {
+                // Translators: String substitution is the account name
+                title = _("Problem connecting to outgoing server for %s".printf(account));
+                // Translators: String substitution is the server name
+                descr = _("Could not connect to %s, check your Internet access and the server name and try 
again").printf(server);
+                retry = _("Try reconnecting now");
+                retry = _("Retry connecting now");
+
+            } else if (report.problem_type == Geary.ProblemType.NETWORK_ERROR &&
+                       service_report.service_type == Geary.Service.IMAP) {
+                // Translators: String substitution is the account name
+                title = _("Problem with connection to incoming server for %s").printf(account);
+                // Translators: String substitution is the server name
+                descr = _("Network error talking to %s, check your Internet access try 
again").printf(server);
+                retry = _("Try reconnecting");
+
+            } else if (report.problem_type == Geary.ProblemType.NETWORK_ERROR &&
+                       service_report.service_type == Geary.Service.SMTP) {
+                // Translators: String substitution is the account name
+                title = _("Problem with connection to outgoing server for %s").printf(account);
+                // Translators: String substitution is the server name
+                descr = _("Network error talking to %s, check your Internet access try 
again").printf(server);
+                retry = _("Try reconnecting");
+
+            } else if (report.problem_type == Geary.ProblemType.SERVER_ERROR &&
+                       service_report.service_type == Geary.Service.IMAP) {
+                // Translators: String substitution is the account name
+                title = _("Problem communicating with incoming server for %s").printf(account);
+                // Translators: String substitution is the server name
+                descr = _("Geary did not understand a message from %s or vice versa, please file a bug 
report").printf(server);
+                retry = _("Try reconnecting");
+
+            } else if (report.problem_type == Geary.ProblemType.SERVER_ERROR &&
+                       service_report.service_type == Geary.Service.SMTP) {
+                title = _("Problem communicating with outgoing mail server");
+                // Translators: First string substitution is the server
+                // name, second is the account name
+                descr = _("Could now communicate with %s for %s, server name and try again in a 
moment").printf(server, account);
+                retry = _("Try reconnecting");
+
+            } else if (report.problem_type == Geary.ProblemType.LOGIN_FAILED &&
+                       service_report.service_type == Geary.Service.IMAP) {
+                // Translators: String substitution is the account name
+                title = _("Incoming mail server password required for %s").printf(account);
+                descr = _("Messages cannot be received without the correct password.");
+                retry = _("Retry receiving email, you will be prompted for a password");
+
+            } else if (report.problem_type == Geary.ProblemType.LOGIN_FAILED &&
+                       service_report.service_type == Geary.Service.SMTP) {
+                // Translators: String substitution is the account name
+                title = _("Outgoing mail server password required for %s").printf(account);
+                descr = _("Messages cannot be sent without the correct password.");
+                retry = _("Retry sending queued messages, you will be prompted for a password");
+
+            } else if (report.problem_type == Geary.ProblemType.GENERIC_ERROR &&
+                       service_report.service_type == Geary.Service.IMAP) {
+                // Translators: String substitution is the account name
+                title = _("A problem occurred checking mail for %s").printf(account);
+                descr = _("Something went wrong, please file a bug report if the problem persists");
+                retry = _("Try reconnecting");
+
+            } else if (report.problem_type == Geary.ProblemType.GENERIC_ERROR &&
+                       service_report.service_type == Geary.Service.SMTP) {
+                // Translators: String substitution is the account name
+                title = _("A problem occurred sending mail for %s").printf(account);
+                descr = _("Something went wrong, please file a bug report if the problem persists");
+                retry = _("Retry sending queued messages");
+
+            } else {
+                debug("Un-handled service problem report: %s".printf(report.to_string()));
+                show_generic = true;
+            }
+        } else if (report is Geary.AccountProblemReport) {
+            Geary.AccountProblemReport account_report = (Geary.AccountProblemReport) report;
+            string account = account_report.account.display_name;
+            if (report.problem_type == Geary.ProblemType.DATABASE_FAILURE) {
+                type = Gtk.MessageType.ERROR;
+                title = _("A database problem has occurred");
+                // Translators: String substitution is the account name
+                descr = _("Messages for %s must be downloaded again.").printf(account);
+                show_close = true;
+
+            } else {
+                debug("Un-handled account problem report: %s".printf(report.to_string()));
+                show_generic = true;
+            }
+        } else {
+            debug("Un-handled generic problem report: %s".printf(report.to_string()));
+            show_generic = true;
+        }
 
-        case Geary.Account.Problem.NETWORK_UNAVAILABLE:
-            title = _("Not connected to the Internet");
-            descr = _("Please check your connection to the Internet.");
+        if (show_generic) {
+            title = _("Geary has encountered a problem");
+            descr = _("Please check the technical details and report the problem if it persists.");
             show_close = true;
-            break;
-
-        case Geary.Account.Problem.RECV_EMAIL_ERROR:
-            type = Gtk.MessageType.ERROR;
-            title = _("A problem occurred checking for new mail");
-            descr = _("New messages can not be received for %s, try again in a moment").printf(name);
-            retry = _("Retry checking for new mail");
-            break;
-
-        case Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED:
-            title = _("Incoming mail password required");
-            descr = _("Messages cannot be received for %s without the correct password.").printf(name);
-            retry = _("Retry receiving email, you will be prompted for a password");
-            break;
-
-        case Geary.Account.Problem.SEND_EMAIL_ERROR:
-            type = Gtk.MessageType.ERROR;
-            title = _("A problem occurred sending mail");
-            descr = _("A message was unable to be sent for %s, try again in a moment").printf(name);
-            retry = _("Retry sending queued messages");
-            break;
-
-        case Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED:
-            title = _("Outgoing mail password required");
-            descr = _("Messages cannot be sent for %s without the correct password.").printf(name);
-            retry = _("Retry sending queued messages, you will be prompted for a password");
-            break;
-
-        default:
-            debug("Un-handled problem type for %s: %s".printf(
-                      account.information.id, problem.to_string()
-                  ));
-            break;
         }
 
         this(type, title, descr, show_close);
-        this.problem = problem;
-        this.account = account;
-        this.error = error;
+        this.report = report;
 
-        if (this.error != null) {
+        if (this.report.error != null) {
             Gtk.Button details = add_button(_("_Details"), ResponseType.DETAILS);
             details.tooltip_text = _("View technical details about the error");
         }
@@ -127,41 +174,52 @@ public class MainWindowInfoBar : Gtk.InfoBar {
                                 bool show_close) {
         this.message_type = type;
         this.title.label = title;
+
+        // Set the label and tooltip for the description in case it is
+        // long enough to be ellipsized
         this.description.label = description;
+        this.description.tooltip_text = description;
+
         this.show_close_button = show_close;
     }
 
     private string format_details() {
-        string type = "";
-        if (this.error != null) {
-            const string QUARK_SUFFIX = "-quark";
-            string ugly_domain = this.error.domain.to_string();
-            if (ugly_domain.has_suffix(QUARK_SUFFIX)) {
-                ugly_domain = ugly_domain.substring(
-                    0, ugly_domain.length - QUARK_SUFFIX.length
-                );
-            }
-            StringBuilder nice_domain = new StringBuilder();
-            foreach (string part in ugly_domain.split("_")) {
-                nice_domain.append(part.up(1));
-                nice_domain.append(part.substring(1));
-            }
+        Geary.ServiceProblemReport? service_report = this.report as Geary.ServiceProblemReport;
+        Geary.AccountProblemReport? account_report = this.report as Geary.AccountProblemReport;
 
-            type = "%s %i".printf(nice_domain.str, this.error.code);
+        StringBuilder details = new StringBuilder();
+        details.append_printf(
+            "Geary version: %s\n", GearyApplication.VERSION
+        );
+        details.append_printf(
+            "GTK version: %u.%u.%u\n", Gtk.get_major_version(), Gtk.get_minor_version(), 
Gtk.get_micro_version()
+        );
+        details.append_printf(
+            "Desktop: %s\n", Environment.get_variable("XDG_CURRENT_DESKTOP") ?? "Unknown"
+        );
+        details.append_printf(
+            "Problem type: %s\n", this.report.problem_type.to_string()
+        );
+        if (account_report != null) {
+            details.append_printf(
+                "Account type: %s\n", account_report.account.service_provider.to_string()
+            );
         }
-
-        return """Geary version: %s
-GTK+ version: %u.%u.%u
-Desktop: %s
-Error type: %s
-Message: %s
-""".printf(
-        GearyApplication.VERSION,
-        Gtk.get_major_version(), Gtk.get_minor_version(), Gtk.get_micro_version(),
-        Environment.get_variable("XDG_CURRENT_DESKTOP") ?? "Unknown",
-        type,
-        (this.error != null) ? error.message : ""
-    );
+        if (service_report != null) {
+            details.append_printf(
+                "Service type: %s\n", service_report.service_type.to_string()
+            );
+            details.append_printf(
+                "Endpoint: %s\n", service_report.endpoint.to_string()
+            );
+        }
+        details.append_printf(
+            "Error type: %s\n", (this.report.error != null) ? this.report.format_error_type() : "None 
specified"
+        );
+        details.append_printf(
+            "Message: %s\n", (this.report.error != null) ? this.report.error.message : "None specified"
+        );
+        return details.str;
     }
 
     private void show_details() {
@@ -170,7 +228,7 @@ Message: %s
         // Would love to construct the dialog in Builder, but we to
         // construct the dialog manually since we can't adjust the
         // Headerbar setting afterwards. If the user re-clicks on the
-        // Details button to re-show it, a whole bunch of GTK
+        // Details button to re-show it, a whole bunch of GTK
         // criticals are spewed and the dialog appears b0rked, so just
         // do it from scratch ever time anyway.
         bool use_header = Gtk.Settings.get_default().gtk_dialogs_use_header;
@@ -181,7 +239,8 @@ Message: %s
         Gtk.Dialog dialog = new Gtk.Dialog.with_buttons(
             _("Details"), // same as the button
             get_toplevel() as Gtk.Window,
-            flags
+            flags,
+            null
         );
         dialog.set_default_size(600, -1);
         dialog.get_content_area().add(this.problem_details);
diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala
index f541674..d9812d2 100644
--- a/src/engine/api/geary-account.vala
+++ b/src/engine/api/geary-account.vala
@@ -27,19 +27,6 @@ public abstract class Geary.Account : BaseObject {
     internal const uint AUTH_ATTEMPTS_MAX = 3;
 
 
-    /** Describes problem types that are reported to the client. */
-    public enum Problem {
-        DATABASE_FAILURE,
-        HOST_UNREACHABLE,
-        NETWORK_UNAVAILABLE,
-        RECV_EMAIL_ERROR,
-        RECV_EMAIL_LOGIN_FAILED,
-        SEND_EMAIL_ERROR,
-        SEND_EMAIL_LOGIN_FAILED,
-        SEND_EMAIL_SAVE_FAILED,
-    }
-
-
     public Geary.AccountInformation information { get; protected set; }
     
     public Geary.ProgressMonitor search_upgrade_monitor { get; protected set; }
@@ -53,8 +40,8 @@ public abstract class Geary.Account : BaseObject {
     public signal void closed();
     
     public signal void email_sent(Geary.RFC822.Message rfc822);
-    
-    public signal void report_problem(Geary.Account.Problem problem, Error? err);
+
+    public signal void report_problem(Geary.ProblemReport problem);
 
     public signal void contacts_loaded();
     
@@ -138,64 +125,7 @@ public abstract class Geary.Account : BaseObject {
         this.name = name;
         this.information = information;
     }
-    
-    protected virtual void notify_folders_available_unavailable(Gee.List<Geary.Folder>? available,
-        Gee.List<Geary.Folder>? unavailable) {
-        folders_available_unavailable(available, unavailable);
-    }
 
-    protected virtual void notify_folders_added_removed(Gee.List<Geary.Folder>? added,
-        Gee.List<Geary.Folder>? removed) {
-        folders_added_removed(added, removed);
-    }
-    
-    protected virtual void notify_folders_contents_altered(Gee.Collection<Geary.Folder> altered) {
-        folders_contents_altered(altered);
-    }
-    
-    protected virtual void notify_email_appended(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> 
ids) {
-        email_appended(folder, ids);
-    }
-    
-    protected virtual void notify_email_inserted(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> 
ids) {
-        email_inserted(folder, ids);
-    }
-    
-    protected virtual void notify_email_removed(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> 
ids) {
-        email_removed(folder, ids);
-    }
-    
-    protected virtual void notify_email_locally_complete(Geary.Folder folder,
-        Gee.Collection<Geary.EmailIdentifier> ids) {
-        email_locally_complete(folder, ids);
-    }
-    
-    protected virtual void notify_email_discovered(Geary.Folder folder,
-        Gee.Collection<Geary.EmailIdentifier> ids) {
-        email_discovered(folder, ids);
-    }
-    
-    protected virtual void notify_email_flags_changed(Geary.Folder folder,
-        Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> flag_map) {
-        email_flags_changed(folder, flag_map);
-    }
-    
-    protected virtual void notify_opened() {
-        opened();
-    }
-    
-    protected virtual void notify_closed() {
-        closed();
-    }
-    
-    protected virtual void notify_email_sent(RFC822.Message message) {
-        email_sent(message);
-    }
-    
-    protected virtual void notify_report_problem(Geary.Account.Problem problem, Error? err) {
-        report_problem(problem, err);
-    }
-    
     /**
      * A utility method to sort a Gee.Collection of {@link Folder}s by their {@link FolderPath}s
      * to ensure they comport with {@link folders_available_unavailable} and
@@ -422,5 +352,86 @@ public abstract class Geary.Account : BaseObject {
     public virtual string to_string() {
         return name;
     }
-}
 
+    /** Fires a {@link opened}} signal. */
+    protected virtual void notify_opened() {
+        opened();
+    }
+
+    /** Fires a {@link closed}} signal. */
+    protected virtual void notify_closed() {
+        closed();
+    }
+
+    /** Fires a {@link folders_available_unavailable}} signal. */
+    protected virtual void notify_folders_available_unavailable(Gee.List<Geary.Folder>? available,
+                                                                Gee.List<Geary.Folder>? unavailable) {
+        folders_available_unavailable(available, unavailable);
+    }
+
+    /** Fires a {@link folders_added_removed}} signal. */
+    protected virtual void notify_folders_added_removed(Gee.List<Geary.Folder>? added,
+        Gee.List<Geary.Folder>? removed) {
+        folders_added_removed(added, removed);
+    }
+
+    /** Fires a {@link folders_contents_altered}} signal. */
+    protected virtual void notify_folders_contents_altered(Gee.Collection<Geary.Folder> altered) {
+        folders_contents_altered(altered);
+    }
+
+    /** Fires a {@link email_appended}} signal. */
+    protected virtual void notify_email_appended(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> 
ids) {
+        email_appended(folder, ids);
+    }
+
+    /** Fires a {@link email_inserted}} signal. */
+    protected virtual void notify_email_inserted(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> 
ids) {
+        email_inserted(folder, ids);
+    }
+
+    /** Fires a {@link email_removed}} signal. */
+    protected virtual void notify_email_removed(Geary.Folder folder, Gee.Collection<Geary.EmailIdentifier> 
ids) {
+        email_removed(folder, ids);
+    }
+
+    /** Fires a {@link email_locally_complete}} signal. */
+    protected virtual void notify_email_locally_complete(Geary.Folder folder,
+        Gee.Collection<Geary.EmailIdentifier> ids) {
+        email_locally_complete(folder, ids);
+    }
+
+    /** Fires a {@link email_discovered}} signal. */
+    protected virtual void notify_email_discovered(Geary.Folder folder,
+        Gee.Collection<Geary.EmailIdentifier> ids) {
+        email_discovered(folder, ids);
+    }
+
+    /** Fires a {@link email_flags_changed}} signal. */
+    protected virtual void notify_email_flags_changed(Geary.Folder folder,
+        Gee.Map<Geary.EmailIdentifier, Geary.EmailFlags> flag_map) {
+        email_flags_changed(folder, flag_map);
+    }
+
+    protected virtual void notify_email_sent(RFC822.Message message) {
+        email_sent(message);
+    }
+
+    /** Fires a {@link report_problem}} signal for this account. */
+    protected virtual void notify_report_problem(ProblemReport report) {
+        report_problem(report);
+    }
+
+    /**
+     * Fires a {@link report_problem}} signal for this account.
+     */
+    protected virtual void notify_account_problem(ProblemType type, Error? err) {
+        report_problem(new AccountProblemReport(type, this.information, err));
+    }
+
+    /** Fires a {@link report_problem}} signal for a service for this account. */
+    protected virtual void notify_service_problem(ProblemType type, Service service_type, Error? err) {
+        report_problem(new ServiceProblemReport(type, this.information, service_type, err));
+    }
+
+}
diff --git a/src/engine/api/geary-problem-report.vala b/src/engine/api/geary-problem-report.vala
new file mode 100644
index 0000000..dd9ad2e
--- /dev/null
+++ b/src/engine/api/geary-problem-report.vala
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2017 Michael Gratton <mike vee net>
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/** Describes available problem types. */
+public enum Geary.ProblemType {
+
+    /** Indicates an engine problem not covered by one of the other types. */
+    GENERIC_ERROR,
+
+    /** Indicates an error opening, using or closing the account database. */
+    DATABASE_FAILURE,
+
+    /** Indicates a problem establishing a connection. */
+    CONNECTION_ERROR,
+
+    /** Indicates a problem caused by a network operation. */
+    NETWORK_ERROR,
+
+    /** Indicates a non-network related server error. */
+    SERVER_ERROR,
+
+    /** Indicates credentials supplied for authentication were rejected. */
+    LOGIN_FAILED,
+
+    /** Indicates an outgoing message was sent, but not saved. */
+    SEND_EMAIL_SAVE_FAILED;
+
+    /** Determines the appropriate problem type for an IOError. */
+    public static ProblemType for_ioerror(IOError error) {
+        if (error is IOError.CONNECTION_REFUSED ||
+            error is IOError.HOST_NOT_FOUND ||
+            error is IOError.HOST_UNREACHABLE ||
+            error is IOError.NETWORK_UNREACHABLE) {
+            return ProblemType.CONNECTION_ERROR;
+        }
+
+        if (error is IOError.CONNECTION_CLOSED ||
+            error is IOError.NOT_CONNECTED) {
+            return ProblemType.NETWORK_ERROR;
+        }
+
+        return ProblemType.GENERIC_ERROR;
+    }
+
+}
+
+/**
+ * Describes a error that the engine encountered, for reporting to the client.
+ */
+public class Geary.ProblemReport : Object {
+
+    /** Describes the type of being reported. */
+    public ProblemType problem_type { get; private set; }
+
+    /** The exception caused the problem, if any. */
+    public Error? error { get; private set; default = null; }
+
+
+    public ProblemReport(ProblemType type, Error? error) {
+        this.problem_type = type;
+        this.error = error;
+    }
+
+    /** Returns a string representation of the report, for debugging only. */
+    public string to_string() {
+        return "%s: %s".printf(
+            this.problem_type.to_string(),
+            format_full_error() ?? "no error reported"
+        );
+    }
+
+    /** Returns a string representation of the error type, for debugging. */
+    public string? format_error_type() {
+        string type = null;
+        if (this.error != null) {
+            const string QUARK_SUFFIX = "-quark";
+            string ugly_domain = this.error.domain.to_string();
+            if (ugly_domain.has_suffix(QUARK_SUFFIX)) {
+                ugly_domain = ugly_domain.substring(
+                    0, ugly_domain.length - QUARK_SUFFIX.length
+                );
+            }
+            StringBuilder nice_domain = new StringBuilder();
+            string separator = (ugly_domain.index_of("_") != -1) ? "_" : "-";
+            foreach (string part in ugly_domain.split(separator)) {
+                if (part.length > 0) {
+                    if (part == "io") {
+                        nice_domain.append("IO");
+                    } else {
+                        nice_domain.append(part.up(1));
+                        nice_domain.append(part.substring(1));
+                    }
+                }
+            }
+
+            type = "%s %i".printf(nice_domain.str, this.error.code);
+        }
+        return type;
+    }
+
+    /** Returns a string representation of the complete error, for debugging. */
+    public string? format_full_error() {
+        string error = null;
+        if (this.error != null) {
+            error = String.is_empty(this.error.message)
+                ? "%s: no message specified".printf(format_error_type())
+                : "%s: \"%s\"".printf(format_error_type(), this.error.message);
+        }
+        return error;
+    }
+
+}
+
+/**
+ * Describes an account-related error that the engine encountered.
+ */
+public class Geary.AccountProblemReport : ProblemReport {
+
+
+    /** The account related to the problem report. */
+    public AccountInformation account { get; private set; }
+
+
+    public AccountProblemReport(ProblemType type, AccountInformation account, Error? error) {
+        base(type, error);
+        this.account = account;
+    }
+
+    /** Returns a string representation of the report, for debugging only. */
+    public new string to_string() {
+        return "%s: %s".printf(this.account.id, base.to_string());
+    }
+
+}
+
+/**
+ * Describes a service-related error that the engine encountered.
+ */
+public class Geary.ServiceProblemReport : AccountProblemReport {
+
+
+    /** The service related to the problem report. */
+    public Service service_type { get; private set; }
+
+    /** The endpoint for the report's service type. */
+    public Endpoint endpoint {
+        owned get {
+            return (this.service_type == Service.IMAP)
+               ? this.account.get_imap_endpoint()
+               : this.account.get_smtp_endpoint();
+        }
+    }
+
+
+    public ServiceProblemReport(ProblemType type, AccountInformation account, Service service_type, Error? 
error) {
+        base(type, account, error);
+        this.service_type = service_type;
+    }
+
+    /** Returns a string representation of the report, for debugging only. */
+    public new string to_string() {
+        return "%s: %s: %s: %s".printf(
+            this.account.id,
+            this.service_type.to_string(),
+            this.problem_type.to_string(),
+            format_full_error() ?? "no error reported"
+        );
+    }
+
+}
diff --git a/src/engine/imap-db/outbox/smtp-outbox-folder.vala 
b/src/engine/imap-db/outbox/smtp-outbox-folder.vala
index bae4525..806a6e8 100644
--- a/src/engine/imap-db/outbox/smtp-outbox-folder.vala
+++ b/src/engine/imap-db/outbox/smtp-outbox-folder.vala
@@ -92,7 +92,7 @@ private class Geary.SmtpOutboxFolder :
     public signal void email_sent(Geary.RFC822.Message rfc822);
 
     /** Fired if a user-notifiable problem occurs. */
-    public signal void report_problem(Geary.Account.Problem problem, Error? err);
+    public signal void report_problem(ProblemReport report);
 
 
     // Requires the Database from the get-go because it runs a background task that access it
@@ -127,42 +127,51 @@ private class Geary.SmtpOutboxFolder :
         while (!cancellable.is_cancelled()) {
             // yield until a message is ready
             OutboxRow? row = null;
+            bool row_handled = false;
             try {
                 row = yield this.outbox_queue.recv_async(cancellable);
-                if (yield postman_send(row, cancellable)) {
-                    // send was good, reset nap length
-                    send_retry_seconds = MIN_SEND_RETRY_INTERVAL_SEC;
-                } else {
-                    // send was bad, try sending again later
-                    this.outbox_queue.send(row);
-
-                    if (!cancellable.is_cancelled()) {
-                        debug("Outbox napping for %u seconds...", send_retry_seconds);
-                        // Take a brief nap before continuing to allow
-                        // connection problems to resolve.
-                        yield Geary.Scheduler.sleep_async(send_retry_seconds);
-                        send_retry_seconds = Geary.Numeric.uint_ceiling(
-                            send_retry_seconds * 2,
-                            MAX_SEND_RETRY_INTERVAL_SEC
-                        );
-                    }
-                }
-            } catch (Error err) {
-                // A hard error occurred. This will cause the postman
-                // to exit but we still want to re-queue the row in
-                // case it restarts.
-                if (row != null) {
-                    this.outbox_queue.send(row);
-                }
-                debug("Outbox postman error: %s", err.message);
+                row_handled = yield postman_send(row, cancellable);
+            } catch (SmtpError err) {
+                ProblemType problem = ProblemType.GENERIC_ERROR;
                 if (err is SmtpError.AUTHENTICATION_FAILED) {
-                    report_problem(Geary.Account.Problem.SEND_EMAIL_LOGIN_FAILED, err);
-                } else if (!(err is IOError.CANCELLED)) {
-                    report_problem(Geary.Account.Problem.SEND_EMAIL_ERROR, err);
+                    problem = ProblemType.LOGIN_FAILED;
+                } else if (err is SmtpError.STARTTLS_FAILED) {
+                    problem = ProblemType.CONNECTION_ERROR;
+                } else if (err is SmtpError.NOT_CONNECTED) {
+                    problem = ProblemType.NETWORK_ERROR;
+                } else if (err is SmtpError.PARSE_ERROR ||
+                           err is SmtpError.SERVER_ERROR ||
+                           err is SmtpError.NOT_SUPPORTED) {
+                    problem = ProblemType.SERVER_ERROR;
                 }
-                // Get out of here
+                notify_report_problem(problem, err);
+                cancellable.cancel();
+            } catch (IOError err) {
+                notify_report_problem(ProblemType.for_ioerror(err), err);
+                cancellable.cancel();
+            } catch (Error err) {
+                notify_report_problem(ProblemType.GENERIC_ERROR, err);
                 cancellable.cancel();
             }
+
+            if (row_handled) {
+                // send was good, reset nap length
+                send_retry_seconds = MIN_SEND_RETRY_INTERVAL_SEC;
+            } else {
+                // send was bad, try sending again later
+                this.outbox_queue.send(row);
+
+                if (!cancellable.is_cancelled()) {
+                    debug("Outbox napping for %u seconds...", send_retry_seconds);
+                    // Take a brief nap before continuing to allow
+                    // connection problems to resolve.
+                    yield Geary.Scheduler.sleep_async(send_retry_seconds);
+                    send_retry_seconds = Geary.Numeric.uint_ceiling(
+                        send_retry_seconds * 2,
+                        MAX_SEND_RETRY_INTERVAL_SEC
+                    );
+                }
+            }
         }
 
         this.queue_cancellable = null;
@@ -554,7 +563,7 @@ private class Geary.SmtpOutboxFolder :
                 yield save_sent_mail_async(message, cancellable);
             } catch (Error err) {
                 debug("Outbox postman: Error saving sent mail: %s", err.message);
-                report_problem(Geary.Account.Problem.SEND_EMAIL_SAVE_FAILED, err);
+                notify_report_problem(ProblemType.SEND_EMAIL_SAVE_FAILED, err);
                 return false;
             }
         }
@@ -866,6 +875,10 @@ private class Geary.SmtpOutboxFolder :
         return stmt.exec_get_modified(cancellable) > 0;
     }
 
+    private void notify_report_problem(ProblemType problem, Error? err) {
+        report_problem(new ServiceProblemReport(problem, this.account.information, Service.SMTP, err));
+    }
+
     private void on_account_opened() {
         this.fill_outbox_queue.begin();
         this.smtp_endpoint.connectivity.notify["is-reachable"].connect(on_reachable_changed);
diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala 
b/src/engine/imap-engine/imap-engine-generic-account.vala
index 5155220..50972e9 100644
--- a/src/engine/imap-engine/imap-engine-generic-account.vala
+++ b/src/engine/imap-engine/imap-engine-generic-account.vala
@@ -455,10 +455,12 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
 
                 successful = true;
             }
+        } catch (ImapError err) {
+            notify_account_problem(ProblemType.SERVER_ERROR, err);
+        } catch (IOError err) {
+            notify_account_problem(ProblemType.for_ioerror(err), err);
         } catch (Error err) {
-            if (!(err is IOError.CANCELLED)) {
-                report_problem(Geary.Account.Problem.RECV_EMAIL_ERROR, err);
-            }
+            notify_account_problem(ProblemType.GENERIC_ERROR, err);
         }
 
         this.enumerate_folder_cancellable = null;
diff --git a/src/engine/imap/api/imap-account.vala b/src/engine/imap/api/imap-account.vala
index 05f98e4..9550873 100644
--- a/src/engine/imap/api/imap-account.vala
+++ b/src/engine/imap/api/imap-account.vala
@@ -45,6 +45,7 @@ private class Geary.Imap.Account : BaseObject {
     private Imap.MailboxSpecifier? inbox_specifier = null;
     private string hierarchy_delimiter = null;
 
+
     /**
      * Fired after opening when the account has a working connection.
      *
@@ -54,7 +55,8 @@ private class Geary.Imap.Account : BaseObject {
     public signal void ready();
 
     /** Fired if a user-notifiable problem occurs. */
-    public signal void report_problem(Geary.Account.Problem problem, Error? err);
+    public signal void report_problem(ProblemReport report);
+
 
     public Account(Geary.AccountInformation account) {
         this.name = account.id + ":imap";
@@ -65,11 +67,6 @@ private class Geary.Imap.Account : BaseObject {
         this.session_mgr.login_failed.connect(on_login_failed);
     }
 
-    private void check_open() throws Error {
-        if (!is_open)
-            throw new EngineError.OPEN_REQUIRED("Imap.Account not open");
-    }
-    
     public async void open_async(Cancellable? cancellable = null) throws Error {
         if (is_open)
             throw new EngineError.ALREADY_OPEN("Imap.Account already open");
@@ -621,7 +618,16 @@ private class Geary.Imap.Account : BaseObject {
         
         return responses;
     }
-    
+
+    private void check_open() throws Error {
+        if (!is_open)
+            throw new EngineError.OPEN_REQUIRED("Imap.Account not open");
+    }
+
+    private void notify_report_problem(ProblemType problem, Error? err) {
+        report_problem(new ServiceProblemReport(problem, this.account, Service.IMAP, err));
+    }
+
     [NoReturn]
     private void throw_not_found(Geary.FolderPath? path) throws EngineError {
         throw new EngineError.NOT_FOUND("Folder %s not found on %s",
@@ -637,10 +643,11 @@ private class Geary.Imap.Account : BaseObject {
     private void on_connection_failed(Error error) {
         // There was an error connecting to the IMAP host
         this.authentication_failures = 0;
-        if (!(error is IOError.CANCELLED)) {
-            // XXX check the type of the error and report a more
-            // fine-grained problem here
-            report_problem(Geary.Account.Problem.RECV_EMAIL_ERROR, error);
+        if (error is ImapError.UNAUTHENTICATED) {
+            // This is effectively a login failure
+            on_login_failed(null);
+        } else {
+            notify_report_problem(ProblemType.CONNECTION_ERROR, error);
         }
     }
 
@@ -648,7 +655,7 @@ private class Geary.Imap.Account : BaseObject {
         this.authentication_failures++;
         if (this.authentication_failures >= Geary.Account.AUTH_ATTEMPTS_MAX) {
             // We have tried auth too many times, so bail out
-            report_problem(Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED, null);
+            notify_report_problem(ProblemType.LOGIN_FAILED, null);
         } else {
             // login can fail due to an invalid password hence we
             // should re-ask it but it can also fail due to server
@@ -673,7 +680,7 @@ private class Geary.Imap.Account : BaseObject {
                 // Either the server was unavailable, or we were unable to
                 // parse the login response. Either way, indicate a
                 // non-login error.
-                report_problem(Geary.Account.Problem.RECV_EMAIL_ERROR, login_error);
+                notify_report_problem(ProblemType.SERVER_ERROR, login_error);
             } else {
                 // Now, we should ask the user for their password
                 this.account.fetch_passwords_async.begin(
@@ -685,10 +692,10 @@ private class Geary.Imap.Account : BaseObject {
                                 this.session_mgr.credentials_updated();
                             } else {
                                 // User cancelled, so indicate a login problem
-                                report_problem(Geary.Account.Problem.RECV_EMAIL_LOGIN_FAILED, null);
+                                notify_report_problem(ProblemType.LOGIN_FAILED, null);
                             }
                         } catch (Error err) {
-                            report_problem(Geary.Account.Problem.RECV_EMAIL_ERROR, err);
+                            notify_report_problem(ProblemType.GENERIC_ERROR, err);
                         }
                     });
             }
diff --git a/ui/main-window-info-bar.ui b/ui/main-window-info-bar.ui
index 465f441..312ffaa 100644
--- a/ui/main-window-info-bar.ui
+++ b/ui/main-window-info-bar.ui
@@ -94,7 +94,7 @@
         <property name="valign">baseline</property>
         <property name="margin_bottom">12</property>
         <property name="hexpand">True</property>
-        <property name="label" translatable="yes">If the problem is serious or persists, please copy and 
send these details to the &lt;a href="https://wiki.gnome.org/Apps/Geary/Contact"&gt;mailing list&lt;/a&gt; or 
lodge a &lt;a href="https://wiki.gnome.org/Apps/Geary/ReportingABug"&gt;bug report&lt;/a&gt;.</property>
+        <property name="label" translatable="yes">If the problem is serious or persists, please copy and 
send these details to the &lt;a href="https://wiki.gnome.org/Apps/Geary/Contact"&gt;mailing list&lt;/a&gt; or 
file a &lt;a href="https://wiki.gnome.org/Apps/Geary/ReportingABug"&gt;new bug report&lt;/a&gt;.</property>
         <property name="use_markup">True</property>
         <property name="wrap">True</property>
         <property name="xalign">0</property>


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