[polari/wip/bastianilso/error-handling: 6/10] errorNotification
- From: Bastian Ilsø Hougaard <bastianilso src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [polari/wip/bastianilso/error-handling: 6/10] errorNotification
- Date: Wed, 29 Jul 2015 22:26:45 +0000 (UTC)
commit 6080bd1b9929b4aad0be762439a1d41651bb1f75
Author: Bastian Ilsø <bastianilso src gnome org>
Date: Tue Jul 28 14:07:41 2015 +0200
errorNotification
src/appNotifications.js | 282 ++++++++++++++++++++++++++++++++++++++++++++---
src/application.js | 94 +++++++++++++---
src/mainWindow.js | 7 +
3 files changed, 348 insertions(+), 35 deletions(-)
---
diff --git a/src/appNotifications.js b/src/appNotifications.js
index 24d6009..b047031 100644
--- a/src/appNotifications.js
+++ b/src/appNotifications.js
@@ -1,31 +1,38 @@
const Gtk = imports.gi.Gtk;
const Tp = imports.gi.TelepathyGLib;
+const GLib = imports.gi.GLib;
+const Gio = imports.gi.Gio;
const Lang = imports.lang;
const Mainloop = imports.mainloop;
+const Connections = imports.connections;
const COMMAND_OUTPUT_REVEAL_TIME = 3;
+const TP_CURRENT_TIME = GLib.MAXUINT32;
const AppNotification = new Lang.Class({
Name: 'AppNotification',
Abstract: true,
_init: function() {
- this.widget = new Gtk.Revealer({ reveal_child: true });
+ this.widget = new Gtk.Revealer({ reveal_child: false });
this.widget.transition_type = Gtk.RevealerTransitionType.SLIDE_DOWN;
-
- this.widget.connect('notify::child-revealed',
- Lang.bind(this, this._onChildRevealed));
},
close: function() {
this.widget.reveal_child = false;
+ this.widget.destroy();
+ this.widget = null;
},
- _onChildRevealed: function() {
- if (!this.widget.child_revealed)
- this.widget.destroy();
- }
+ open: function() {
+ this.widget.reveal_child = true;
+ },
+
+
+ get statusReason() {
+ return this._statusReason;
+ },
});
const CommandOutputNotification = new Lang.Class({
@@ -89,42 +96,283 @@ const GridOutput = new Lang.Class({
}
});
+const ErrorNotification = new Lang.Class({
+ Name: 'ErrorNotification',
+ Extends: AppNotification,
+
+ _init: function(requestData) {
+ this.parent();
+ //log('creating error notification..');
+ this._grid = new Gtk.Grid({ orientation: Gtk.Orientation.HORIZONTAL,
+ column_spacing: 12 });
+ this._app = Gio.Application.get_default();
+ this._roomId = requestData.roomId;
+ this._grid.add(new Gtk.Image({icon_name: 'dialog-error-symbolic' }));
+ this._account = requestData.account;
+ this._window = requestData.window;
+ this._button = new Gtk.Button({ valign: Gtk.Align.CENTER });
+ this._label = new Gtk.Label({ max_width_chars: 30, wrap: true });
+ this._populateNotification(requestData.error);
+ this._grid.add(this._label);
+ this._grid.add(this._button);
+ this.widget.add(this._grid);
+ this.widget.show_all();
+ this._statusReason = requestData.error;
+ },
+
+ _populateNotification: function(error) {
+ if (error == Tp.error_get_dbus_name(Tp.Error.NETWORK_ERROR)) {
+ // Network Error is given
+ // - when the server adress is misspelled
+ // - when you disconnect while connecting and reconnect (then you disconnect temporarily again
with this error and reconnect shortly after)
+ // - when you try to connect to a port that only supports SSL, but didn't check off "use-ssl"
(fx by specifying a wrong port number).
+ this._label.label = _("Unable to connect to %s").format(this._account.display_name);
+ this._button.label = _("Edit Account");
+ this._button.connect('clicked', Lang.bind(this, this._editAccountAction));
+ } else if (error == Tp.error_get_dbus_name(Tp.Error.AUTHENTICATION_FAILED)) {
+ this._label.label = _("Authentication failed for %s.").format(this._account.display_name);
+ this._button.label = _("Retry");
+ this._button.connect('clicked', Lang.bind(this, this._reconnectAccountAction));
+ } else if (error == Tp.error_get_dbus_name(Tp.Error.SERVICE_BUSY)) {
+ this._label.label = _("%s is too busy at the moment.").format(this._account.display_name);
+ this._button.label = _("Retry");
+ this._button.connect('clicked', Lang.bind(this, this._reconnectAccountAction));
+ } else if (error == Tp.error_get_dbus_name(Tp.Error.CHANNEL_BANNED)) {
+ this._label.label = _("You are banned from this room.");
+ this._button.label = _("Retry");
+ this._button.connect('clicked', Lang.bind(this, this._joinRoomAction));
+ } else if (error == Tp.error_get_dbus_name(Tp.Error.CHANNEL_FULL)) {
+ this._label.label = _("The room is full.");
+ this._button.label = _("Retry");
+ this._button.connect('clicked', Lang.bind(this, this._joinRoomAction));
+ } else if (error == Tp.error_get_dbus_name(Tp.Error.CHANNEL_INVITE_ONLY)) {
+ this._label.label = _("The room is invite-only.");
+ this._button.label = _("Retry");
+ this._button.connect('clicked', Lang.bind(this, this._joinRoomAction));
+ } else if (error == Tp.error_get_dbus_name(Tp.Error.ENCRYPTION_ERROR)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_NOT_PROVIDED)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_UNTRUSTED)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_EXPIRED)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_NOT_ACTIVATED)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_HOSTNAME_MISMATCH)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_FINGERPRINT_MISMATCH)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_SELF_SIGNED)
+ || error == Tp.error_get_dbus_name(Tp.Error.ENCRYPTION_NOT_AVAILABLE)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_INVALID)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_REVOKED)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_INSECURE)
+ || error == Tp.error_get_dbus_name(Tp.Error.CERT_LIMIT_EXCEEDED)) {
+ // Not a fan of this label, but it's the best i've got. Don't want to expose words like
"certificate", "encryption" and "wobblegobble"
+ // If we do automatic SSL it might come as a surprise to the user who might not even worry about
it..
+ // "The connection to %s is public." <- indicating that everyone can potentially read what you
write? :S
+ // Show "Unlocked" symbol instead of error icon? or show world icon.
+ // "Everyone can see what you read and write on this connection." Subtitle?
+ this._label.label = _("The connection to %s is not safe.").format(this._account.display_name);
+ // More like "For this connection it's okay." or "For this connection I don't care" button.
+ this._button.label = _("Continue Anyway");
+ this._button.connect('clicked', Lang.bind(this, this._reconnectNoEncryption));
+ } else {
+ log('no match for: ' + error);
+ this._label.label = _("Failed to connect for an unknown reason.");
+ this._button.label = _("Retry");
+ this._button.connect('clicked', Lang.bind(this, this._joinRoomAction));
+ }
+ },
+
+ _editAccountAction: function(button) {
+ let dialog = new Connections.ConnectionDetailsDialog(this._account);
+ dialog.widget.transient_for = this._window;
+ dialog.widget.show();
+ dialog.widget.connect('response', Lang.bind(this,
+ function(w, response) {
+ dialog.widget.destroy();
+
+ if (response != Gtk.ResponseType.OK)
+ return;
+
+ this._reconnectAccountAction();
+ this.close();
+ }));
+ },
+
+ _reconnectAccountAction: function() {
+ // Not sure we actually need this..
+ let action = this._app.lookup_action('reconnect-account');
+ let accountPath = GLib.Variant.new('o', this._account.get_object_path());
+ action.activate(accountPath);
+ this.close();
+ },
+
+ _reconnectNoEncryption: function() {
+ let oldDetails = this._account.dup_parameters_vardict().deep_unpack();
+ log(oldDetails);
+ let details = { account: GLib.Variant.new('s', oldDetails.nickname),
+ server: GLib.Variant.new('s', oldDetails.server) };
+
+ if (oldDetails.fullname)
+ details.fullname = GLib.Variant.new('s', oldDetails.fullname);
+
+ details.port = GLib.Variant.new('u', 6667);
+ details['use-ssl'] = GLib.Variant.new('b', false);
+
+ let removed = Object.keys(oldDetails).filter(
+ function(p) {
+ return !details.hasOwnProperty(p);
+ });
+
+ let vardict = GLib.Variant.new('a{sv}', details);
+ this._account.update_parameters_vardict_async(vardict, removed,
+ Lang.bind(this, function(a, res) {
+ a.update_parameters_vardict_finish(res); // TODO: Check for errors
+ this._reconnectAccountAction();
+ }));
+ },
+
+ _joinRoomAction: function() {
+ let action = this._app.lookup_action('join-room');
+ let regex = /(?:#|&)(.+)/g;
+ let room = this._roomId.match(regex);
+ if (room)
+ action.activate(GLib.Variant.new('(ssu)',
+ [ this._account.get_object_path(),
+ room[0],
+ TP_CURRENT_TIME ]));
+ this.close();
+ },
+});
+
const NotificationQueue = new Lang.Class({
Name: 'NotificationQueue',
_init: function() {
this.widget = new Gtk.Frame({ valign: Gtk.Align.START,
halign: Gtk.Align.CENTER,
- no_show_all: true });
+ no_show_all: false });
this.widget.get_style_context().add_class('app-notification');
this._grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
row_spacing: 6, visible: true });
this.widget.add(this._grid);
+ this._notifications = {};
+ this._activeNotifications = {};
+ let initParams = {};
+ this._activeNotifications['room'] = initParams;
+ this._activeNotifications['account'] = initParams;
+ this._updateVisibility();
},
- addNotification: function(notification) {
- this._grid.add(notification.widget);
+ setNotification: function(params) {
+ this._notifications[params.identifier] = params;
+ //log('checking if ' + params.identifier + ' is active..');
+ //log('this._activeNotifications[' + params.type + '].identifier: ' +
this._activeNotifications[params.type].identifier)
+ if (this._activeNotifications[params.type].identifier != params.identifier)
+ return;
+ if (this._activeNotifications[params.type].notification == params.notification)
+ return;
+ //log('..true. calling displayNotification.');
+ this._displayNotification(params);
+ },
- notification.widget.connect('destroy',
- Lang.bind(this, this._onChildDestroy));
+ loadNotifications: function(identifier, type) {
+ //log('current notice is ' + this._activeNotifications[type].notification
+ // + ' and is loaded for: ' + this._activeNotifications[type].identifier);
+ // We only perform this check if activeNotification has already been initialized.
+ // Otherwise we'll never be able to load notifications from this._activeNotifications.
+ if (this._activeNotifications[type]) {
+ //log('loadNotifications: checking archived notification == displayed notification..');
+ //log('activeNotification for ' + type + ': ' + this._activeNotifications[type]);
+ /*log('this._notifications[identifier] for ' + identifier + ': ' +
this._notifications[identifier]);
+ if (this._notifications[identifier]) {
+ log('.notification for ' + identifier + ': ' + this._notifications[identifier].notification);
+ if (this._notifications[identifier].notification)
+ log('.notification.widget for ' + identifier + ': ' +
this._notifications[identifier].notification.widget);
+ }*/
+ if (this._activeNotifications[type] == this._notifications[identifier]) {
+ //log('match between active notification and current notification!');
+ return;
+ }
+ //log('loadNotifications: notifications did not match');
+ }
+
+ let notification = this._notifications[identifier] ? this._notifications[identifier].notification :
null;
+ let params = { notification: notification,
+ type: type, identifier: identifier };
+
+ this._displayNotification(params);
+ },
+
+ _displayNotification: function(params) {
+ //log('now displaying ' + params.notification + ' for: ' + params.identifier);
+
+ //log('_displayNotification: checking if any notifications of that type is visible..');
+ if (this._activeNotifications[params.type].notification) {
+ //log('_displayNotification: true. removing visible notification');
+ //this._activeNotifications[params.type].notification.close();
+ //log('grid children: ' + this._grid.get_children().length);
+ if (this._activeNotifications[params.type].notification.widget) {
+ //log('removing ' + this._activeNotifications[params.type].notification);
+ // If the next notification is null and we're still displaying for the same room.
+ // Then we should just make the active notification null.
+ if (!params.notification && params.identifier ==
this._activeNotifications[params.type].identifier) {
+ this._activeNotifications[params.type].notification.close();
+ } else {
+ this._grid.remove(this._activeNotifications[params.type].notification.widget);
+ }
+ }
+ }
+ //log('storing parameters inside this._activeNotifications[' + params.type + ']..');
+ this._activeNotifications[params.type] = params;
+ //log('_displayNotification: checking if our notification is empty...');
+ if (!params.notification) {
+ //log('_displayNotification: notification is empty, returning..');
+ this._updateVisibility();
+ return;
+ }
+
+ this._grid.add(params.notification.widget);
+ params.notification.open();
+ params.notification.widget.connect('destroy',
+ Lang.bind(this, this._updateVisibility));
this.widget.show();
},
- _onChildDestroy: function() {
- if (this._grid.get_children().length == 0)
+ _updateVisibility: function() {
+ //log('amount of children in tree: ' + this._grid.get_children().length);
+ if (this._grid.get_children().length == 0) {
+ //log('hiding widget..');
this.widget.hide();
+ }
}
});
const CommandOutputQueue = new Lang.Class({
Name: 'CommandOutputQueue',
- Extends: NotificationQueue,
_init: function() {
- this.parent();
+ this.widget = new Gtk.Frame({ valign: Gtk.Align.START,
+ halign: Gtk.Align.CENTER,
+ no_show_all: true });
+ this.widget.get_style_context().add_class('app-notification');
+
+ this._grid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
+ row_spacing: 6, visible: true });
+ this.widget.add(this._grid);
this.widget.valign = Gtk.Align.END;
this.widget.get_style_context().add_class('irc-feedback');
+ },
+
+ addNotification: function(notification) {
+ this._grid.add(notification.widget);
+
+ notification.widget.connect('destroy',
+ Lang.bind(this, this._onChildDestroy));
+ this.widget.show();
+ notification.open();
+ },
+
+ _onChildDestroy: function() {
+ if (this._grid.get_children().length == 0)
+ this.widget.hide();
}
});
diff --git a/src/application.js b/src/application.js
index 7262bfd..d603c5a 100644
--- a/src/application.js
+++ b/src/application.js
@@ -46,7 +46,8 @@ const Application = new Lang.Class({
function(am, account) {
this._removeSavedChannelsForAccount(account);
}));
-
+ this._accountsMonitor.connect('account-status-changed',
+ Lang.bind(this, this._onAccountChanged));
this._settings = new Gio.Settings({ schema_id: 'org.gnome.Polari' });
this.pasteManager = new PasteManager.PasteManager();
@@ -77,6 +78,9 @@ const Application = new Lang.Class({
activate: Lang.bind(this, this._onLeaveCurrentRoom),
create_hook: Lang.bind(this, this._leaveRoomCreateHook),
accels: ['<Primary>w'] },
+ { name: 'reconnect-account',
+ activate: Lang.bind(this, this._onReconnectAccount),
+ parameter_type: GLib.VariantType.new('o') },
{ name: 'user-list',
activate: Lang.bind(this, this._onToggleAction),
create_hook: Lang.bind(this, this._userListCreateHook),
@@ -191,6 +195,21 @@ const Application = new Lang.Class({
this._window.showMessageUserDialog();
},
+ _onAccountChanged: function(am, account) {
+ // We reset the notification when account changes because
+ // External parties can make the account reconnect while
+ // we are showing an error.
+ // Fx. the case where we are try to connect, then disconnect, then reconnect.
+ // We get a network error and then somehow we begin reconnecting again.
+ log('account changed to ' + account.connection_error + ', resetting notification.');
+ let accountError = { notification: null,
+ type: 'account',
+ identifier: account.get_path_suffix() };
+ this.notificationQueue.setNotification(accountError);
+
+
+ },
+
_addSavedChannel: function(account, channel) {
let savedChannels = this._settings.get_value('saved-channel-list').deep_unpack();
let savedChannel = {
@@ -273,8 +292,14 @@ const Application = new Lang.Class({
},
_ensureChannel: function(requestData) {
+ this.mark_busy();
let account = requestData.account;
+ let roomError = { notification: null,
+ type: 'room',
+ identifier: requestData.roomId };
+ this.notificationQueue.setNotification(roomError);
+
let req = Tp.AccountChannelRequest.new_text(account, requestData.time);
req.set_target_id(requestData.targetHandleType, requestData.targetId);
req.set_delegate_to_preferred_handler(true);
@@ -284,41 +309,64 @@ const Application = new Lang.Class({
this._onEnsureChannel, requestData));
},
- _retryRequest: function(requestData) {
- let account = requestData.account;
-
- // Try again with a different nick
- let params = account.dup_parameters_vardict().deep_unpack();
- let oldNick = params['account'].deep_unpack();
- let nick = oldNick + '_';
- this._updateAccountName(account, nick, Lang.bind(this,
- function() {
- this._ensureChannel(requestData);
- }));
- },
-
_onEnsureChannel: function(req, res, requestData) {
let account = req.account;
-
+ let roomError = { notification: null,
+ type: 'room',
+ identifier: requestData.roomId };
+ let accountError = { notification: null,
+ type: 'account',
+ identifier: account.get_path_suffix() };
try {
req.ensure_channel_finish(res);
if (requestData.targetHandleType == Tp.HandleType.ROOM)
this._addSavedChannel(account, requestData.targetId);
} catch (e if e.matches(Tp.Error, Tp.Error.DISCONNECTED)) {
- let error = account.connection_error;
+ //let error = account.connection_error;
+ // If we receive an error and the network is unavailable,
+ // then the error is not specific to polari and polari will
+ // just be in offline state.
+ let networkMonitor = Gio.NetworkMonitor.get_default();
+ if (!networkMonitor.network_available)
+ return;
+ log('dbus error: ' + account.connection_error);
+ //let error = Tp.error_get_dbus_name(Tp.Error.CERT_NOT_ACTIVATED);
if (error == ConnectionError.ALREADY_CONNECTED &&
requestData.retry++ < MAX_RETRIES) {
- this._retryRequest(requestData);
+ let params = account.dup_parameters_vardict().deep_unpack();
+ let oldNick = params['account'].deep_unpack();
+ let nick = oldNick + '_';
+ this._updateAccountName(account, nick, Lang.bind(this,
+ function() {
+ this._ensureChannel(requestData);
+ }));
return;
}
- if (error && error != ConnectionError.CANCELLED)
+ if (error && error != ConnectionError.CANCELLED) {
logError(e);
+ requestData.error = error;
+ requestData.window = this._window.window;
+ let notification = new AppNotifications.ErrorNotification(requestData);
+ /*notification.widget.connect('destroy',
+ Lang.bind(this, function() {
+ this._ensureChannel(requestData);
+ }));*/
+ accountError.notification = notification;
+ }
} catch (e if e.matches(Tp.Error, Tp.Error.CANCELLED)) {
// interrupted by user request, don't log
} catch (e) {
logError(e, 'Failed to ensure channel');
+ let error = Tp.error_get_dbus_name(e.code);
+ requestData.error = error;
+ requestData.window = this._window.window;
+ roomError.notification = new AppNotifications.ErrorNotification(requestData);
+ } finally {
+ this.unmark_busy();
+ this.notificationQueue.setNotification(roomError);
+ this.notificationQueue.setNotification(accountError);
}
if (requestData.retry > 0)
@@ -332,6 +380,16 @@ const Application = new Lang.Class({
channelName, time);
},
+ _onReconnectAccount: function(action, parameter) {
+ let factory = Tp.AccountManager.dup().get_factory();
+ let accountPath = parameter.unpack();
+ log(accountPath);
+ let account = factory.ensure_account(accountPath, []);
+ account.reconnect_async(Lang.bind(this, function(a, res) {
+ account.reconnect_finish(res);
+ }));
+ },
+
_onMessageUser: function(action, parameter) {
let [accountPath, contactName, time] = parameter.deep_unpack();
this._requestChannel(accountPath, Tp.HandleType.CONTACT,
diff --git a/src/mainWindow.js b/src/mainWindow.js
index 2443d10..ff49d6b 100644
--- a/src/mainWindow.js
+++ b/src/mainWindow.js
@@ -158,6 +158,13 @@ const MainWindow = new Lang.Class({
this._membersChangedId =
this._room.connect('members-changed',
Lang.bind(this, this._updateUserListLabel));
+ let app = Gio.Application.get_default();
+ log('**********')
+ log('loading archived notifications for: ' + room.account.get_path_suffix());
+ app.notificationQueue.loadNotifications(room.account.get_path_suffix(), 'account');
+ log('**')
+ log('loading archived notifications for: ' + room.id);
+ app.notificationQueue.loadNotifications(room.id, 'room');
},
_createWidget: function(app) {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]