[geary/wip/713006-better-error-reporting] Allow passing addtional information when reporting engine errors.
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/713006-better-error-reporting] Allow passing addtional information when reporting engine errors.
- Date: Mon, 13 Nov 2017 12:40:44 +0000 (UTC)
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 <a href="https://wiki.gnome.org/Apps/Geary/Contact">mailing list</a> or
lodge a <a href="https://wiki.gnome.org/Apps/Geary/ReportingABug">bug report</a>.</property>
+ <property name="label" translatable="yes">If the problem is serious or persists, please copy and
send these details to the <a href="https://wiki.gnome.org/Apps/Geary/Contact">mailing list</a> or
file a <a href="https://wiki.gnome.org/Apps/Geary/ReportingABug">new bug report</a>.</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]