[polari/wip/fmuellner/misc-fixes: 10/16] app: Handle errors on connection status changes



commit 291f4f1cd63aa6917140094538dac3b64ff10c7e
Author: Florian Müllner <fmuellner gnome org>
Date:   Mon Aug 1 02:25:14 2016 +0200

    app: Handle errors on connection status changes
    
    We try to recover from failed connection attempts by trying alternative
    servers or using different account names, but only when handling the
    'join-room' and 'message-user' actions - if the attempt was initiated
    by any other mean, for instance the (re)connect items in the room list,
    our error handling will be sidestepped completely.
    So rather than handling exceptions that occur in the ensure_channels()
    call, handle errors when the connection status changes from CONNECTING
    to DISCONNECTED, which works independently from how the connection
    was initiated.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=769583

 src/application.js |  210 +++++++++++++++++++++++++---------------------------
 1 files changed, 100 insertions(+), 110 deletions(-)
---
diff --git a/src/application.js b/src/application.js
index cd12a8a..e87fa56 100644
--- a/src/application.js
+++ b/src/application.js
@@ -18,15 +18,6 @@ const MAX_RETRIES = 3;
 
 const IRC_SCHEMA_REGEX = /^(irc?:\/\/)([\da-z\.-]+):?(\d+)?\/(?:%23)?([\w\.\+-]+)/i;
 
-const ConnectionError = {
-    CANCELLED: Tp.error_get_dbus_name(Tp.Error.CANCELLED),
-    ALREADY_CONNECTED: Tp.error_get_dbus_name(Tp.Error.ALREADY_CONNECTED),
-    DISCONNECTED: Tp.error_get_dbus_name(Tp.Error.DISCONNECTED),
-    NETWORK_ERROR: Tp.error_get_dbus_name(Tp.Error.NETWORK_ERROR),
-    NOT_AVAILABLE: Tp.error_get_dbus_name(Tp.Error.NOT_AVAILABLE),
-    SERVICE_BUSY: Tp.error_get_dbus_name(Tp.Error.SERVICE_BUSY)
-};
-
 const Application = new Lang.Class({
     Name: 'Application',
     Extends: Gtk.Application,
@@ -39,6 +30,7 @@ const Application = new Lang.Class({
         GLib.set_application_name('Polari');
         this._window = null;
         this._pendingRequests = new Map();
+        this._retryData = new Map();
     },
 
     vfunc_startup: function() {
@@ -53,6 +45,8 @@ const Application = new Lang.Class({
             function(am, account) {
                 this._removeSavedChannelsForAccount(account.object_path);
             }));
+        this._accountsMonitor.connect('account-status-changed',
+                                      Lang.bind(this, this._onAccountStatusChanged));
 
         this._settings = new Gio.Settings({ schema_id: 'org.gnome.Polari' });
 
@@ -333,21 +327,6 @@ const Application = new Lang.Class({
                                  GLib.Variant.new('aa{sv}', savedChannels));
     },
 
-    _updateAccountName: function(account, name, callback) {
-        let sv = { account: GLib.Variant.new('s', name) };
-        let asv = GLib.Variant.new('a{sv}', sv);
-        account.update_parameters_vardict_async(asv, [], callback);
-    },
-
-    _updateAccountServer: function(account, server, callback) {
-        let sv = { server: GLib.Variant.new('s', server.address),
-                   port: GLib.Variant.new('u', server.port),
-                   'use-ssl': GLib.Variant.new('b', server.ssl)
-                    };
-        let asv = GLib.Variant.new('a{sv}', sv);
-        account.update_parameters_vardict_async(asv, [], callback);
-    },
-
     _requestChannel: function(accountPath, targetType, targetId, time, callback) {
         let account = this._accountsMonitor.lookupAccount(accountPath);
 
@@ -362,117 +341,128 @@ const Application = new Lang.Class({
             return;
 
         let roomId = Polari.create_room_id(account,  targetId, targetType);
+        let cancellable = new Gio.Cancellable();
+        this._pendingRequests.set(roomId, cancellable);
+
+        let req = Tp.AccountChannelRequest.new_text(account, time);
+        req.set_target_id(targetType, targetId);
+        req.set_delegate_to_preferred_handler(true);
+        let preferredHandler = Tp.CLIENT_BUS_NAME_BASE + 'Polari';
+        req.ensure_and_observe_channel_async(preferredHandler, cancellable,
+            (o, res) => {
+                let channel = null;
+                try {
+                    channel = req.ensure_and_observe_channel_finish(res);
+                } catch(e) {
+                    Utils.debug('Failed to ensure channel: ' + e.message);
+                }
+
+                if (callback)
+                    callback(channel);
+                delete this._pendingRequests[roomId];
+            });
+    },
+
+    _ensureRetryData: function(account) {
+        let data = this._retryData.get(account.object_path);
+        if (data)
+            return data;
 
         let params = account.dup_parameters_vardict().deep_unpack();
         let server = params['server'].deep_unpack();
-        let accountServers = [];
+        let nick = params['account'].deep_unpack();
+        Utils.debug('Failed to connect to %s with username %s'.format(server, nick));
 
-        // If predefined network, get alternate servers list
+        let accountServers = [];
         if (this._networksManager.getAccountIsPredefined(account))
             accountServers = this._networksManager.getNetworkServers(account.service);
 
-        let requestData = {
-          account: account,
-          targetHandleType: targetType,
-          targetId: targetId,
-          roomId: roomId,
-          cancellable: new Gio.Cancellable(),
-          time: time,
-          retry: 0,
-          originalNick: params['account'].deep_unpack(),
-          callback: callback,
-          alternateServers: accountServers.filter(s => s.address != server)
+        data = {
+            retry: 0,
+            originalNick: nick,
+            alternateServers: accountServers.filter(s => s.address != server)
         };
+        this._retryData.set(account.object_path, data);
+        return data;
+    },
 
-        this._pendingRequests.set(roomId, requestData.cancellable);
+    _clearRetryData: function(account) {
+        let data = this._retryData.get(account.object_path);
+        if (!data)
+            return;
 
-        this._ensureChannel(requestData);
+        if (data.retry > 0) {
+            let params = { account: new GLib.Variant('s', data.originalNick) };
+            let asv = new GLib.Variant('a{sv}', params);
+            account.update_parameters_vardict_async(asv, [], null);
+        }
+        this._retryData.delete(account.object_path);
     },
 
-    _ensureChannel: function(requestData) {
-        let account = requestData.account;
-
-        let req = Tp.AccountChannelRequest.new_text(account, requestData.time);
-        req.set_target_id(requestData.targetHandleType, requestData.targetId);
-        req.set_delegate_to_preferred_handler(true);
-        let preferredHandler = Tp.CLIENT_BUS_NAME_BASE + 'Polari';
-        req.ensure_and_observe_channel_async(preferredHandler, requestData.cancellable,
-                                 Lang.bind(this,
-                                           this._onEnsureChannel, requestData));
+    _retryWithParams: function(account, params) {
+        account.update_parameters_vardict_async(params, [], () => {
+            let presence = Tp.ConnectionPresenceType.AVAILABLE;
+            let msg = account.requested_status_message;
+            account.request_presence_async(presence, 'available', msg, null);
+        });
     },
 
-    _retryNickRequest: function(requestData) {
-        let account = requestData.account;
+    _retryNickRequest: function(account) {
+        let retryData = this._ensureRetryData(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);
-            }));
+        if (retryData.retry++ >= MAX_RETRIES)
+            return false;
+
+        let oldParams = account.dup_parameters_vardict().deep_unpack();
+        let nick = oldParams['account'].deep_unpack();
+
+        Utils.debug('Retrying with nickname %s'.format(nick + '_'));
+        let params = { account: new GLib.Variant('s', nick + '_') };
+        this._retryWithParams(account, new GLib.Variant('a{sv}', params));
+        return true;
     },
 
-    _retryServerRequest: function(requestData) {
-        let account = requestData.account;
+    _retryServerRequest: function(account) {
+        let retryData = this._ensureRetryData(account);
 
-        // Try again with a alternate server
-        let server = requestData.alternateServers.shift();
-        this._updateAccountServer(account, server, Lang.bind(this,
-            function() {
-                this._ensureChannel(requestData);
-            }));
+        let server = retryData.alternateServers.shift();
+        if (!server)
+            return false;
+
+        Utils.debug('Retrying with %s:%d'.format(server.address, server.port));
+        let params = { server: new GLib.Variant('s', server.address),
+                       port: new GLib.Variant('u', server.port),
+                       'use-ssl': new GLib.Variant('b', server.ssl) };
+        this._retryWithParams(account, new GLib.Variant('a{sv}', params));
+        return true;
     },
 
-    _onEnsureChannel: function(req, res, requestData) {
-        let account = req.account;
-        let channel = null;
+    _onAccountStatusChanged: function(mon, account) {
+        let status = account.connection_status;
 
-        try {
-            channel = req.ensure_and_observe_channel_finish(res);
-        } catch (e if e.matches(Tp.Error, Tp.Error.DISCONNECTED)) {
-            // If we receive a disconnect error and the network is unavailable,
-            // then the error is not specific to polari and polari will
-            // just be in offline state.
-            if (!this._networkMonitor.network_available)
-                return;
+        if (status == Tp.ConnectionStatus.CONNECTING)
+            return;
 
-            let error = account.connection_error;
-            switch (error) {
-                case ConnectionError.CANCELLED:
-                    break; // disconnected due to user request, ignore
-                case ConnectionError.ALREADY_CONNECTED:
-                    if (requestData.retry++ < MAX_RETRIES) {
-                        this._retryNickRequest(requestData);
-                        return;
-                    }
-                    break;
-                case ConnectionError.NETWORK_ERROR:
-                case ConnectionError.NOT_AVAILABLE:
-                case ConnectionError.DISCONNECTED:
-                case ConnectionError.SERVICE_BUSY:
-                    if (requestData.alternateServers.length > 0) {
-                        this._retryServerRequest(requestData);
-                        return;
-                    }
-                default:
-                    Utils.debug('Account %s disconnected with error %s'.format(
-                                account.get_path_suffix(),
-                                error.replace(Tp.ERROR_PREFIX + '.', '')));
+        if (status == Tp.ConnectionStatus.DISCONNECTED) {
+            let reason = account.connection_status_reason;
+
+            if (reason == Tp.ConnectionStatusReason.NAME_IN_USE)
+                if (this._retryNickRequest(account))
+                    return;
+
+            if (reason == Tp.ConnectionStatusReason.NETWORK_ERROR ||
+                reason == Tp.ConnectionStatusReason.NONE_SPECIFIED)
+                if (this._retryServerRequest(account))
+                    return;
+
+            if (reason != Tp.ConnectionStatusReason.REQUESTED) {
+                let strReasons = Object.keys(Tp.ConnectionStatusReason);
+                Utils.debug('Account %s disconnected with reason %s'.format(
+                            account.display_name, strReasons[reason]));
             }
-        } catch (e if e.matches(Tp.Error, Tp.Error.CANCELLED)) {
-            // interrupted by user request, don't log
-        } catch (e) {
-            Utils.debug('Failed to ensure channel: ' + e.message);
         }
 
-        if (requestData.callback)
-            requestData.callback(channel);
-
-        if (requestData.retry > 0)
-            this._updateAccountName(account, requestData.originalNick, null);
-        this._pendingRequests.delete(requestData.roomId);
+        this._clearRetryData(account);
     },
 
     _onJoinRoom: function(action, parameter) {


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