diff --git a/gajim/chat_control.py b/gajim/chat_control.py index f01cda5fa..f729ecdb7 100644 --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -1230,14 +1230,16 @@ class ChatControl(ChatControlBase): return self.update_ui() - def _nec_ping_reply(self, obj): - if obj.control: - if obj.control != self: - return - else: - if self.contact != obj.contact: - return - self.print_conversation(_('Pong! (%s s.)') % obj.seconds, 'status') + def _nec_ping(self, obj): + if self.contact != obj.contact: + return + if obj.name == 'ping-sent': + self.print_conversation(_('Ping?'), 'status') + elif obj.name == 'ping-reply': + self.print_conversation( + _('Pong! (%s s.)') % obj.seconds, 'status') + elif obj.name == 'ping-error': + self.print_conversation(_('Error.'), 'status') def show_avatar(self): if not app.config.get('show_avatar_in_chat'): diff --git a/gajim/chat_control_base.py b/gajim/chat_control_base.py index 3fde497f2..c73f3d6df 100644 --- a/gajim/chat_control_base.py +++ b/gajim/chat_control_base.py @@ -193,16 +193,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): if self.parent_win: self.parent_win.redraw_tab(self) - def _nec_ping_sent(self, obj): - if self.contact != obj.contact: - return - self.print_conversation(_('Ping?'), 'status') - - def _nec_ping_error(self, obj): - if self.contact != obj.contact: - return - self.print_conversation(_('Error.'), 'status') - def status_url_clicked(self, widget, url): helpers.launch_browser_mailer('url', url) @@ -381,11 +371,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools): app.ged.register_event_handler('our-show', ged.GUI1, self._nec_our_status) app.ged.register_event_handler('ping-sent', ged.GUI1, - self._nec_ping_sent) + self._nec_ping) app.ged.register_event_handler('ping-reply', ged.GUI1, - self._nec_ping_reply) + self._nec_ping) app.ged.register_event_handler('ping-error', ged.GUI1, - self._nec_ping_error) + self._nec_ping) # This is basically a very nasty hack to surpass the inability # to properly use the super, because of the old code. diff --git a/gajim/command_system/implementation/standard.py b/gajim/command_system/implementation/standard.py index 175ad277a..88e0d9f6a 100644 --- a/gajim/command_system/implementation/standard.py +++ b/gajim/command_system/implementation/standard.py @@ -178,7 +178,7 @@ class StandardCommonChatCommands(CommandContainer): def ping(self): if self.account == app.ZEROCONF_ACC_NAME: raise CommandError(_('Command is not supported for zeroconf accounts')) - app.connections[self.account].sendPing(self.contact) + app.connections[self.account].get_module('Ping').send_ping(self.contact) @command @doc(_("Send DTMF sequence through an open audio session")) @@ -391,5 +391,5 @@ class StandardGroupChatCommands(CommandContainer): if self.account == app.ZEROCONF_ACC_NAME: raise CommandError(_('Command is not supported for zeroconf accounts')) gc_c = app.contacts.get_gc_contact(self.account, self.room_jid, nick) - app.connections[self.account].sendPing(gc_c, self) + app.connections[self.account].get_module('Ping').send_ping(gc_c) diff --git a/gajim/common/connection.py b/gajim/common/connection.py index 0e4eee441..734481356 100644 --- a/gajim/common/connection.py +++ b/gajim/common/connection.py @@ -65,6 +65,7 @@ from gajim.common import i18n from gajim.common import idle from gajim.common.modules.entity_time import EntityTime from gajim.common.modules.software_version import SoftwareVersion +from gajim.common.modules.ping import Ping from gajim.common.connection_handlers import * from gajim.common.contacts import GC_Contact from gajim.gtkgui_helpers import get_action @@ -661,6 +662,7 @@ class Connection(CommonConnection, ConnectionHandlers): self.register_module('EntityTime', EntityTime, self) self.register_module('SoftwareVersion', SoftwareVersion, self) + self.register_module('Ping', Ping, self) app.ged.register_event_handler('privacy-list-received', ged.CORE, self._nec_privacy_list_received) @@ -1527,45 +1529,6 @@ class Connection(CommonConnection, ConnectionHandlers): if self.connection: self.connection.send(' ') - def _on_xmpp_ping_answer(self, iq_obj): - self.awaiting_xmpp_ping_id = None - - def sendPing(self, pingTo=None, control=None): - """ - Send XMPP Ping (XEP-0199) request. If pingTo is not set, ping is sent to - server to detect connection failure at application level - If control is set, display result there - """ - if not app.account_is_connected(self.name): - return - id_ = self.connection.getAnID() - if pingTo: - to = pingTo.get_full_jid() - app.nec.push_incoming_event(PingSentEvent(None, conn=self, - contact=pingTo)) - else: - to = app.config.get_per('accounts', self.name, 'hostname') - self.awaiting_xmpp_ping_id = id_ - iq = nbxmpp.Iq('get', to=to) - iq.addChild(name='ping', namespace=nbxmpp.NS_PING) - iq.setID(id_) - def _on_response(resp): - timePong = time.time() - if not nbxmpp.isResultNode(resp): - app.nec.push_incoming_event(PingErrorEvent(None, conn=self, - contact=pingTo)) - return - timeDiff = round(timePong - timePing, 2) - app.nec.push_incoming_event(PingReplyEvent(None, conn=self, - contact=pingTo, seconds=timeDiff, control=control)) - if pingTo: - timePing = time.time() - self.connection.SendAndCallForResponse(iq, _on_response) - else: - self.connection.SendAndCallForResponse(iq, self._on_xmpp_ping_answer) - app.idlequeue.set_alarm(self.check_pingalive, app.config.get_per( - 'accounts', self.name, 'time_for_ping_alive_answer')) - def get_active_default_lists(self): if not app.account_is_connected(self.name): return @@ -1832,8 +1795,10 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection = con if not app.account_is_connected(self.name): return + self.connection.set_send_timeout(self.keepalives, self.send_keepalive) - self.connection.set_send_timeout2(self.pingalives, self.sendPing) + self.connection.set_send_timeout2( + self.pingalives, self.get_module('Ping').send_keepalive_ping) self.connection.onreceive(None) self.privacy_rules_requested = False @@ -2963,15 +2928,6 @@ class Connection(CommonConnection, ConnectionHandlers): self.connection.send(message) - def check_pingalive(self): - if not app.config.get_per('accounts', self.name, 'active'): - # Account may have been disabled - return - if self.awaiting_xmpp_ping_id: - # We haven't got the pong in time, disco and reconnect - log.warning("No reply received for keepalive ping. Reconnecting.") - self.disconnectedReconnCB() - def _reconnect_alarm(self): if not app.config.get_per('accounts', self.name, 'active'): # Account may have been disabled diff --git a/gajim/common/connection_handlers.py b/gajim/common/connection_handlers.py index 7e26380b5..eae112b11 100644 --- a/gajim/common/connection_handlers.py +++ b/gajim/common/connection_handlers.py @@ -1328,8 +1328,7 @@ ConnectionHTTPUpload): self.disco_items_ids = [] # IDs of disco#info requests self.disco_info_ids = [] - # ID of urn:xmpp:ping requests - self.awaiting_xmpp_ping_id = None + self.continue_connect_info = None self.privacy_default_list = None @@ -1363,8 +1362,6 @@ ConnectionHTTPUpload): self._nec_roster_received) app.ged.register_event_handler('iq-error-received', ged.CORE, self._nec_iq_error_received) - app.ged.register_event_handler('ping-received', ged.CORE, - self._nec_ping_received) app.ged.register_event_handler('subscribe-presence-received', ged.CORE, self._nec_subscribe_presence_received) app.ged.register_event_handler('subscribed-presence-received', @@ -1404,8 +1401,6 @@ ConnectionHTTPUpload): self._nec_roster_received) app.ged.remove_event_handler('iq-error-received', ged.CORE, self._nec_iq_error_received) - app.ged.remove_event_handler('ping-received', ged.CORE, - self._nec_ping_received) app.ged.remove_event_handler('subscribe-presence-received', ged.CORE, self._nec_subscribe_presence_received) app.ged.remove_event_handler('subscribed-presence-received', @@ -1902,23 +1897,6 @@ ConnectionHTTPUpload): app.nec.push_incoming_event(MucAdminReceivedEvent(None, conn=self, stanza=iq_obj)) - def _IqPingCB(self, con, iq_obj): - log.debug('IqPingCB') - app.nec.push_incoming_event(PingReceivedEvent(None, conn=self, - stanza=iq_obj)) - raise nbxmpp.NodeProcessed - - def _nec_ping_received(self, obj): - if obj.conn.name != self.name: - return - if not self.connection or self.connected < 2: - return - iq_obj = obj.stanza.buildReply('result') - q = iq_obj.getTag('ping') - if q: - iq_obj.delChild(q) - self.connection.send(iq_obj) - def _PrivacySetCB(self, con, iq_obj): """ Privacy lists (XEP 016) @@ -2196,7 +2174,6 @@ ConnectionHTTPUpload): nbxmpp.NS_DISCO_INFO) con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get', nbxmpp.NS_DISCO_ITEMS) - con.RegisterHandler('iq', self._IqPingCB, 'get', nbxmpp.NS_PING) con.RegisterHandler('iq', self._SearchCB, 'result', nbxmpp.NS_SEARCH) con.RegisterHandler('iq', self._PrivacySetCB, 'set', nbxmpp.NS_PRIVACY) con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_1) diff --git a/gajim/common/connection_handlers_events.py b/gajim/common/connection_handlers_events.py index 554a7db1e..429365c5b 100644 --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -573,10 +573,6 @@ class IqErrorReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.errcode = self.stanza.getErrorCode() return True -class PingReceivedEvent(nec.NetworkIncomingEvent): - name = 'ping-received' - base_network_events = [] - class StreamReceivedEvent(nec.NetworkIncomingEvent): name = 'stream-received' base_network_events = [] @@ -1970,18 +1966,6 @@ class ConnectionLostEvent(nec.NetworkIncomingEvent): show='offline')) return True -class PingSentEvent(nec.NetworkIncomingEvent): - name = 'ping-sent' - base_network_events = [] - -class PingReplyEvent(nec.NetworkIncomingEvent): - name = 'ping-reply' - base_network_events = [] - -class PingErrorEvent(nec.NetworkIncomingEvent): - name = 'ping-error' - base_network_events = [] - class CapsPresenceReceivedEvent(nec.NetworkIncomingEvent, HelperEvent, PresenceHelperEvent): name = 'caps-presence-received' diff --git a/gajim/common/modules/ping.py b/gajim/common/modules/ping.py new file mode 100644 index 000000000..8324dbde0 --- /dev/null +++ b/gajim/common/modules/ping.py @@ -0,0 +1,122 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see . + +# XEP-0199: XMPP Ping + +import logging +import time + +import nbxmpp + +from gajim.common import app +from gajim.common.nec import NetworkIncomingEvent + +log = logging.getLogger('gajim.c.m.ping') + + +class Ping: + def __init__(self, con): + self._con = con + self._account = con.name + self._alarm_time = None + + self.handlers = [ + ('iq', self._answer_request, 'get', nbxmpp.NS_PING), + ] + + def _get_ping_iq(self, to): + iq = nbxmpp.Iq('get', to=to) + iq.addChild(name='ping', namespace=nbxmpp.NS_PING) + return iq + + def send_keepalive_ping(self): + if not app.account_is_connected(self._account): + return + + to = app.config.get_per('accounts', self._account, 'hostname') + self._con.connection.SendAndCallForResponse(self._get_ping_iq(to), + self._keepalive_received) + + log.info('Send keepalive') + + seconds = app.config.get_per('accounts', self._account, + 'time_for_ping_alive_answer') + self._alarm_time = app.idlequeue.set_alarm(self._reconnect, seconds) + + def _keepalive_received(self, stanza): + log.info('Received keepalive') + app.idlequeue.remove_alarm(self._reconnect, self._alarm_time) + + def _reconnect(self): + if not app.config.get_per('accounts', self._account, 'active'): + # Account may have been disabled + return + + # We haven't got the pong in time, disco and reconnect + log.warning('No reply received for keepalive ping. Reconnecting...') + self._con.disconnectedReconnCB() + + def send_ping(self, contact): + if not app.account_is_connected(self._account): + return + + to = contact.get_full_jid() + iq = self._get_ping_iq(to) + + log.info('Send ping to %s', to) + + self._con.connection.SendAndCallForResponse( + iq, self._pong_received, {'ping_time': time.time(), + 'contact': contact}) + + app.nec.push_incoming_event( + PingSentEvent(None, conn=self._con, contact=contact)) + + def _pong_received(self, con, stanza, ping_time, contact): + if not nbxmpp.isResultNode(stanza): + log.info('Error: %s', stanza.getError()) + app.nec.push_incoming_event( + PingErrorEvent(None, conn=self._con, contact=contact)) + return + diff = round(time.time() - ping_time, 2) + log.info('Received pong from %s after %s seconds', + stanza.getFrom(), diff) + app.nec.push_incoming_event( + PingReplyEvent(None, conn=self._con, + contact=contact, + seconds=diff)) + + def _answer_request(self, con, stanza): + iq = stanza.buildReply('result') + q = iq.getTag('ping') + if q is not None: + iq.delChild(q) + self._con.connection.send(iq) + log.info('Send pong to %s', stanza.getFrom()) + raise nbxmpp.NodeProcessed + + +class PingSentEvent(NetworkIncomingEvent): + name = 'ping-sent' + base_network_events = [] + + +class PingReplyEvent(NetworkIncomingEvent): + name = 'ping-reply' + base_network_events = [] + + +class PingErrorEvent(NetworkIncomingEvent): + name = 'ping-error' + base_network_events = [] diff --git a/gajim/groupchat_control.py b/gajim/groupchat_control.py index 7da7b3863..982526cf5 100644 --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -1572,14 +1572,18 @@ class GroupchatControl(ChatControlBase): obj.xhtml, self.session, msg_log_id=obj.msg_log_id, encrypted=obj.encrypted, displaymarking=obj.displaymarking) - def _nec_ping_reply(self, obj): - if obj.control: - if obj.control != self: - return - else: - if self.contact != obj.contact: - return - self.print_conversation(_('Pong! (%s s.)') % obj.seconds) + def _nec_ping(self, obj): + if self.contact.jid != obj.contact.room_jid: + return + + nick = obj.contact.get_shown_name() + if obj.name == 'ping-sent': + self.print_conversation(_('Ping? (%s)') % nick) + elif obj.name == 'ping-reply': + self.print_conversation( + _('Pong! (%s %s s.)') % (nick, obj.seconds)) + elif obj.name == 'ping-error': + self.print_conversation(_('Error.')) def got_connected(self): # Make autorejoin stop.