Show details for status-based service problems

This breaks out the problem details dialog out into a seperate class so
it can be used for both problem reports and status problems. Pass in any
service errors to the main window when updating account status, show a
Details button on the service status info bar and show the details
dialog when clicked.
This commit is contained in:
Michael Gratton 2019-01-07 23:13:30 +11:00
parent 92353bf0c9
commit 4ef81050f5
10 changed files with 327 additions and 207 deletions

View file

@ -62,6 +62,7 @@ src/client/conversation-viewer/conversation-web-view.vala
src/client/dialogs/alert-dialog.vala
src/client/dialogs/attachment-dialog.vala
src/client/dialogs/certificate-warning-dialog.vala
src/client/dialogs/dialogs-problem-details-dialog.vala
src/client/dialogs/password-dialog.vala
src/client/dialogs/preferences-dialog.vala
src/client/dialogs/upgrade-dialog.vala
@ -428,4 +429,5 @@ ui/main-window.ui
ui/main-window-info-bar.ui
ui/password-dialog.glade
ui/preferences-dialog.ui
ui/problem-details-dialog.vala
ui/upgrade_dialog.glade

View file

@ -891,7 +891,8 @@ public class GearyController : Geary.BaseObject {
private void report_problem(Geary.ProblemReport report) {
debug("Problem reported: %s", report.to_string());
if (!(report.error is IOError.CANCELLED)) {
if (report.error == null ||
!(report.error.thrown is IOError.CANCELLED)) {
MainWindowInfoBar info_bar = new MainWindowInfoBar.for_problem(report);
info_bar.retry.connect(on_retry_problem);
this.main_window.show_infobar(info_bar);
@ -900,16 +901,23 @@ public class GearyController : Geary.BaseObject {
private void update_account_status() {
Geary.Account.Status effective_status = 0;
bool auth_error = false;
bool has_auth_error = false;
Geary.Account? service_problem_source = null;
foreach (AccountContext context in this.accounts.values) {
effective_status |= context.get_effective_status();
auth_error |= context.authentication_failed;
if (effective_status.has_service_problem() &&
service_problem_source == null) {
service_problem_source = context.account;
}
has_auth_error |= context.authentication_failed;
}
foreach (Gtk.Window window in this.application.get_windows()) {
MainWindow? main = window as MainWindow;
if (main != null) {
main.update_account_status(effective_status, auth_error);
main.update_account_status(
effective_status, has_auth_error, service_problem_source
);
}
}
}

View file

@ -12,7 +12,7 @@
public class MainWindowInfoBar : Gtk.InfoBar {
private enum ResponseType { COPY, DETAILS, RETRY; }
private enum ResponseType { DETAILS, RETRY; }
/** If reporting a problem, returns the problem report else null. */
public Geary.ProblemReport? report { get; private set; default = null; }
@ -27,12 +27,6 @@ public class MainWindowInfoBar : Gtk.InfoBar {
[GtkChild]
private Gtk.Label description;
[GtkChild]
private Gtk.Grid problem_details;
[GtkChild]
private Gtk.TextView detail_text;
public MainWindowInfoBar.for_problem(Geary.ProblemReport report) {
Gtk.MessageType type = Gtk.MessageType.WARNING;
@ -182,107 +176,13 @@ public class MainWindowInfoBar : Gtk.InfoBar {
this.show_close_button = show_close;
}
private string format_details() {
Geary.ServiceProblemReport? service_report = this.report as Geary.ServiceProblemReport;
Geary.AccountProblemReport? account_report = this.report as Geary.AccountProblemReport;
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()
);
}
if (service_report != null) {
details.append_printf(
"Service type: %s\n",
service_report.service.protocol.to_string()
);
details.append_printf(
"Service host: %s\n",
service_report.service.host
);
}
if (this.report.error == null) {
details.append("No error reported");
} else {
details.append_printf(
"Error type: %s\n", this.report.error.format_error_type()
);
details.append_printf(
"Message: %s\n", this.report.error.thrown.message
);
details.append("Back trace:\n");
foreach (Geary.ErrorContext.StackFrame frame in
this.report.error.backtrace) {
details.append_printf(" - %s\n", frame.to_string());
}
}
return details.str;
}
private void show_details() {
this.detail_text.buffer.text = format_details();
// 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
// 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;
Gtk.DialogFlags flags = Gtk.DialogFlags.MODAL;
if (use_header) {
flags |= Gtk.DialogFlags.USE_HEADER_BAR;
}
Gtk.Dialog dialog = new Gtk.Dialog.with_buttons(
_("Details"), // same as the button
get_toplevel() as Gtk.Window,
flags,
null
Dialogs.ProblemDetailsDialog dialog =
new Dialogs.ProblemDetailsDialog.for_problem_report(
get_toplevel() as Gtk.Window, this.report
);
dialog.set_default_size(600, -1);
dialog.get_content_area().add(this.problem_details);
Gtk.HeaderBar? header_bar = dialog.get_header_bar() as Gtk.HeaderBar;
use_header = (header_bar != null);
if (use_header) {
header_bar.show_close_button = true;
} else {
dialog.add_button(_("_Close"), Gtk.ResponseType.CLOSE);
}
Gtk.Widget copy = dialog.add_button(
_("Copy to Clipboard"), ResponseType.COPY
);
copy.tooltip_text =
_("Copy technical details to clipboard for pasting into an email or bug report");
dialog.set_default_response(ResponseType.COPY);
dialog.response.connect(on_details_response);
dialog.show();
copy.grab_focus();
}
private void copy_details() {
get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(format_details(), -1);
dialog.run();
dialog.destroy();
}
[GtkCallback]
@ -308,19 +208,4 @@ public class MainWindowInfoBar : Gtk.InfoBar {
this.parent.remove(this);
}
private void on_details_response(Gtk.Dialog dialog, int response) {
switch(response) {
case ResponseType.COPY:
copy_details();
break;
default:
// fml
dialog.get_content_area().remove(this.problem_details);
dialog.hide();
break;
}
}
}

View file

@ -33,6 +33,7 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
public ConversationViewer conversation_viewer { get; private set; default = new ConversationViewer(); }
public StatusBar status_bar { get; private set; default = new StatusBar(); }
private MonitoredSpinner spinner = new MonitoredSpinner();
[GtkChild]
private Gtk.Box main_layout;
[GtkChild]
@ -65,12 +66,16 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
[GtkChild]
private Gtk.InfoBar service_problem_infobar;
[GtkChild]
private Gtk.Button service_problem_details;
[GtkChild]
private Gtk.InfoBar cert_problem_infobar;
[GtkChild]
private Gtk.InfoBar auth_problem_infobar;
private Geary.Account? service_problem_account = null;
/** Fired when the user requests an account status be retried. */
public signal void retry_service_problem(Geary.ClientService.Status problem);
@ -105,7 +110,8 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
/** Updates the window's account status info bars. */
public void update_account_status(Geary.Account.Status status,
bool auth_error) {
bool has_auth_error,
Geary.Account? service_problem) {
// Only ever show one at a time. Offline is primary since
// nothing else can happen when offline. Service problems are
// secondary since auth and cert problems can't be resolved
@ -120,12 +126,15 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
show_offline = true;
} else if (status.has_service_problem()) {
show_service = true;
} else if (auth_error) {
} else if (has_auth_error) {
show_auth = true;
}
this.service_problem_account = service_problem;
this.offline_infobar.set_visible(show_offline);
this.service_problem_infobar.set_visible(show_service);
this.service_problem_details.set_visible(get_problem_service() != null);
this.cert_problem_infobar.hide();
this.auth_problem_infobar.set_visible(show_auth);
update_infobar_frame();
@ -512,6 +521,18 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
}
}
private Geary.ClientService? get_problem_service() {
Geary.ClientService? service = null;
if (this.service_problem_account != null) {
if (this.service_problem_account.incoming.last_error != null) {
service = this.service_problem_account.incoming;
} else if (this.service_problem_account.outgoing.last_error != null) {
service = this.service_problem_account.outgoing;
}
}
return service;
}
[GtkCallback]
private bool on_focus_event() {
on_shift_key(false);
@ -543,6 +564,22 @@ public class MainWindow : Gtk.ApplicationWindow, Geary.BaseInterface {
retry_service_problem(Geary.ClientService.Status.CONNECTION_FAILED);
}
[GtkCallback]
private void on_service_problem_details() {
Geary.ClientService? service = get_problem_service();
if (service != null) {
Dialogs.ProblemDetailsDialog dialog =
new Dialogs.ProblemDetailsDialog(
this,
service.last_error,
this.service_problem_account.information,
service.configuration
);
dialog.run();
dialog.destroy();
}
}
[GtkCallback]
private void on_cert_problem_retry() {
this.cert_problem_infobar.hide();

View file

@ -0,0 +1,104 @@
/*
* Copyright 2019 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.
*/
/**
* Displays technical details when a problem has been reported.
*/
[GtkTemplate (ui = "/org/gnome/Geary/problem-details-dialog.ui")]
public class Dialogs.ProblemDetailsDialog : Gtk.Dialog {
private Geary.ErrorContext error;
private Geary.AccountInformation? account;
private Geary.ServiceInformation? service;
[GtkChild]
private Gtk.TextView detail_text;
public ProblemDetailsDialog(Gtk.Window parent,
Geary.ErrorContext error,
Geary.AccountInformation? account,
Geary.ServiceInformation? service) {
Object(use_header_bar: 1);
set_default_size(600, -1);
this.error = error;
this.account = account;
this.service = service;
this.detail_text.buffer.text = format_details();
}
public ProblemDetailsDialog.for_problem_report(Gtk.Window parent,
Geary.ProblemReport report) {
Geary.ServiceProblemReport? service_report =
report as Geary.ServiceProblemReport;
Geary.AccountProblemReport? account_report =
report as Geary.AccountProblemReport;
this(
parent,
report.error,
account_report != null ? account_report.account : null,
service_report != null ? service_report.service : null
);
}
private string format_details() {
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"
);
if (this.account != null) {
details.append_printf(
"Account type: %s\n",
this.account.service_provider.to_string()
);
}
if (this.service != null) {
details.append_printf(
"Service type: %s\n",
this.service.protocol.to_string()
);
details.append_printf(
"Service host: %s\n",
this.service.host
);
}
if (this.error == null) {
details.append("No error reported");
} else {
details.append_printf(
"Error type: %s\n", this.error.format_error_type()
);
details.append_printf(
"Message: %s\n", this.error.thrown.message
);
details.append("Back trace:\n");
foreach (Geary.ErrorContext.StackFrame frame in
this.error.backtrace) {
details.append_printf(" - %s\n", frame.to_string());
}
}
return details.str;
}
[GtkCallback]
private void on_copy_clicked() {
get_clipboard(Gdk.SELECTION_CLIPBOARD).set_text(format_details(), -1);
}
}

View file

@ -62,6 +62,7 @@ geary_client_vala_sources = files(
'dialogs/alert-dialog.vala',
'dialogs/attachment-dialog.vala',
'dialogs/certificate-warning-dialog.vala',
'dialogs/dialogs-problem-details-dialog.vala',
'dialogs/password-dialog.vala',
'dialogs/preferences-dialog.vala',
'dialogs/upgrade-dialog.vala',

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.0 -->
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="MainWindowInfoBar" parent="GtkInfoBar">
@ -77,77 +77,4 @@
</packing>
</child>
</template>
<object class="GtkGrid" id="problem_details">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">18</property>
<property name="margin_right">18</property>
<property name="margin_top">18</property>
<property name="margin_bottom">18</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">start</property>
<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 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>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">baseline</property>
<property name="label" translatable="yes">Details:</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="width_request">600</property>
<property name="height_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="detail_text">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="editable">False</property>
<property name="wrap_mode">word</property>
<property name="left_margin">6</property>
<property name="right_margin">6</property>
<property name="top_margin">6</property>
<property name="bottom_margin">6</property>
<property name="cursor_visible">False</property>
<property name="monospace">True</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
</interface>

View file

@ -243,12 +243,12 @@ You will not be able to send or receive email until it is re-connected.</propert
<property name="spacing">6</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes" comments="Button label for retrying an account problem">Retry</property>
<property name="visible">True</property>
<object class="GtkButton" id="service_problem_details">
<property name="label" translatable="yes" comments="Button label for displaying technical details about an account problem">Details</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<signal name="clicked" handler="on_service_problem_retry" swapped="no"/>
<property name="tooltip_text" translatable="yes" comments="Button tooltip for displaying technical details about an account problem">View technical details about the error</property>
<signal name="clicked" handler="on_service_problem_details" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
@ -256,6 +256,21 @@ You will not be able to send or receive email until it is re-connected.</propert
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes" comments="Button label for retrying an account problem">Retry</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes" comments="Button tooltip for retrying an account problem">Retry connecting now</property>
<signal name="clicked" handler="on_service_problem_retry" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -340,10 +355,11 @@ Please check your Internet connection, the server configuration and try again.</
<property name="layout_style">end</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes" comments="Button label for retrying an account problem">Retry</property>
<property name="label" translatable="yes" comments="Button label for retrying TLS cert validation">Check</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes" comments="Button tooltip for retrying TLS cert validation">Check the security details for the connection</property>
<signal name="clicked" handler="on_cert_problem_retry" swapped="no"/>
</object>
<packing>
@ -436,10 +452,11 @@ Please check the server configuration and try again.</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes" comments="Button label for retrying an account problem">Retry</property>
<property name="label" translatable="yes" comments="Button label for retrying when a login error has occurred">Login</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes" comments="Button tooltip for retrying when a login error has occurred">Retry login, you will be prompted for your password</property>
<signal name="clicked" handler="on_auth_problem_retry" swapped="no"/>
</object>
<packing>

View file

@ -35,6 +35,7 @@
<file compressed="true" preprocess="xml-stripblanks">main-window-info-bar.ui</file>
<file compressed="true" preprocess="xml-stripblanks">password-dialog.glade</file>
<file compressed="true" preprocess="xml-stripblanks">preferences-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">problem-details-dialog.ui</file>
<file compressed="true" preprocess="xml-stripblanks">upgrade_dialog.glade</file>
<file compressed="true">geary.css</file>
</gresource>

View file

@ -0,0 +1,138 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.22.1 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<template class="DialogsProblemDetailsDialog" parent="GtkDialog">
<property name="can_focus">False</property>
<property name="modal">True</property>
<property name="type_hint">dialog</property>
<child type="titlebar">
<object class="GtkHeaderBar">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="title" translatable="yes" comments="Dialog title for displaying technical details of a problem. Same as the button that invokes it.">Details</property>
<property name="show_close_button">True</property>
<child>
<object class="GtkButton">
<property name="label" translatable="yes" comments="Button label for copying technical information to the clipboard">Copy to Clipboard</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="tooltip_text" translatable="yes" comments="Button tooltip for copying technical information to the clipboard">Copy technical details to clipboard for pasting into an email or bug report</property>
<signal name="clicked" handler="on_copy_clicked" swapped="no"/>
<style>
<class name="suggested-action"/>
</style>
</object>
<packing>
<property name="pack_type">end</property>
</packing>
</child>
</object>
</child>
<child internal-child="vbox">
<object class="GtkBox">
<property name="can_focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">2</property>
<child internal-child="action_area">
<object class="GtkButtonBox">
<property name="can_focus">False</property>
<property name="layout_style">end</property>
<child>
<placeholder/>
</child>
<child>
<placeholder/>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkGrid" id="problem_details">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="margin_left">18</property>
<property name="margin_right">18</property>
<property name="margin_top">18</property>
<property name="margin_bottom">18</property>
<property name="row_spacing">6</property>
<property name="column_spacing">12</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="halign">start</property>
<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 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>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can_focus">False</property>
<property name="halign">start</property>
<property name="valign">baseline</property>
<property name="label" translatable="yes">Details:</property>
<property name="track_visited_links">False</property>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">1</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="width_request">600</property>
<property name="height_request">200</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="shadow_type">in</property>
<child>
<object class="GtkTextView" id="detail_text">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hexpand">True</property>
<property name="vexpand">True</property>
<property name="editable">False</property>
<property name="wrap_mode">word</property>
<property name="left_margin">6</property>
<property name="right_margin">6</property>
<property name="top_margin">6</property>
<property name="bottom_margin">6</property>
<property name="cursor_visible">False</property>
<property name="monospace">True</property>
</object>
</child>
</object>
<packing>
<property name="left_attach">0</property>
<property name="top_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</template>
</interface>