From 2e2e16e281be81e4f19e45021fe23fb3196ada83 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 15 Feb 2011 15:24:58 -0800 Subject: [PATCH 1/7] fixes to ping: auto-ping off by default, fixed ping-time of zero bug, fixed class name mismatch --- sleekxmpp/plugins/xep_0199/ping.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sleekxmpp/plugins/xep_0199/ping.py b/sleekxmpp/plugins/xep_0199/ping.py index 064af4c..d1e08e6 100644 --- a/sleekxmpp/plugins/xep_0199/ping.py +++ b/sleekxmpp/plugins/xep_0199/ping.py @@ -54,7 +54,7 @@ class xep_0199(base_plugin): self.xep = '0199' self.stanza = stanza - self.keepalive = self.config.get('keepalive', True) + self.keepalive = self.config.get('keepalive', False) self.frequency = float(self.config.get('frequency', 300)) self.timeout = self.config.get('timeout', 30) @@ -90,7 +90,7 @@ class xep_0199(base_plugin): """Send ping request to the server.""" log.debug("Pinging...") resp = self.send_ping(self.xmpp.boundjid.host, self.timeout) - if not resp: + if resp is None or resp is False: log.debug("Did not recieve ping back in time." + \ "Requesting Reconnect.") self.xmpp.reconnect() @@ -160,4 +160,4 @@ class xep_0199(base_plugin): # Backwards compatibility for names -Ping.sendPing = Ping.send_ping +xep_0199.sendPing = xep_0199.send_ping From 4df3aa569bc8e88b70986022e558ee9024dd7ffb Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 23 Feb 2011 10:09:53 -0500 Subject: [PATCH 2/7] Bring back the signal handlers (and the "killed" event). Now done more responsibly, saving any existing signal handlers and calling them when an interrupt occurs in addition to the one Sleek installs. NOTE: You may need to explicitly use "kill " in order to trigger the proper signal handler execution, and to raise the "killed" event. --- sleekxmpp/xmlstream/xmlstream.py | 51 ++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index a5151d7..18d891a 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -10,6 +10,7 @@ from __future__ import with_statement, unicode_literals import copy import logging +import signal import socket as Socket import ssl import sys @@ -195,6 +196,53 @@ class XMLStream(object): self.auto_reconnect = True self.is_client = False + def use_signals(self, signals=None): + """ + Register signal handlers for SIGHUP and SIGTERM, if possible, + which will raise a "killed" event when the application is + terminated. + + If a signal handler already existed, it will be executed first, + before the "killed" event is raised. + + Arguments: + signals -- A list of signal names to be monitored. + Defaults to ['SIGHUP', 'SIGTERM']. + """ + if signals is None: + signals = ['SIGHUP', 'SIGTERM'] + + existing_handlers = {} + for sig_name in signals: + if hasattr(signal, sig_name): + sig = getattr(signal, sig_name) + handler = signal.getsignal(sig) + if handler: + existing_handlers[sig] = handler + + def handle_kill(signum, frame): + """ + Capture kill event and disconnect cleanly after first + spawning the "killed" event. + """ + + if signum in existing_handlers and \ + existing_handlers[signum] != handle_kill: + existing_handlers[signum](signum, frame) + + self.event("killed", direct=True) + self.disconnect() + + try: + for sig_name in signals: + if hasattr(signal, sig_name): + sig = getattr(signal, sig_name) + signal.signal(sig, handle_kill) + self.__signals_installed = True + except: + log.debug("Can not set interrupt signal handlers. " + \ + "SleekXMPP is not running from a main thread.") + def new_id(self): """ Generate and return a new stream ID in hexadecimal form. @@ -731,6 +779,7 @@ class XMLStream(object): if not self.stop.isSet() and self.auto_reconnect: self.reconnect() else: + self.event('killed', direct=True) self.disconnect() self.event_queue.put(('quit', None, None)) self.scheduler.run = False @@ -909,6 +958,7 @@ class XMLStream(object): return False except KeyboardInterrupt: log.debug("Keyboard Escape Detected in _event_runner") + self.event('killed', direct=True) self.disconnect() return except SystemExit: @@ -934,6 +984,7 @@ class XMLStream(object): self.disconnect(self.auto_reconnect) except KeyboardInterrupt: log.debug("Keyboard Escape Detected in _send_thread") + self.event('killed', direct=True) self.disconnect() return except SystemExit: From 77251452c106618ab1cfdad546eb224bc9693dea Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 24 Feb 2011 12:10:29 -0500 Subject: [PATCH 3/7] Updated the XEP-0085 plugin. Can now be used as so: >>> msg['chat_state'] '' >>> msg >>> msg['chat_state'] = 'paused' >>> msg >>> msg['chat_state'] 'paused' >>> del msg['chat_state'] >>> msg --- setup.py | 1 + sleekxmpp/plugins/xep_0085.py | 104 ---------------------- sleekxmpp/plugins/xep_0085/__init__.py | 10 +++ sleekxmpp/plugins/xep_0085/chat_states.py | 48 ++++++++++ sleekxmpp/plugins/xep_0085/stanza.py | 73 +++++++++++++++ tests/test_stanza_xep_0085.py | 37 ++++---- 6 files changed, 149 insertions(+), 124 deletions(-) delete mode 100644 sleekxmpp/plugins/xep_0085.py create mode 100644 sleekxmpp/plugins/xep_0085/__init__.py create mode 100644 sleekxmpp/plugins/xep_0085/chat_states.py create mode 100644 sleekxmpp/plugins/xep_0085/stanza.py diff --git a/setup.py b/setup.py index ae8cf68..4575a07 100644 --- a/setup.py +++ b/setup.py @@ -50,6 +50,7 @@ packages = [ 'sleekxmpp', 'sleekxmpp/plugins/xep_0030', 'sleekxmpp/plugins/xep_0030/stanza', 'sleekxmpp/plugins/xep_0059', + 'sleekxmpp/plugins/xep_0085', 'sleekxmpp/plugins/xep_0092', 'sleekxmpp/plugins/xep_0199', ] diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py deleted file mode 100644 index 3627e71..0000000 --- a/sleekxmpp/plugins/xep_0085.py +++ /dev/null @@ -1,104 +0,0 @@ -""" - SleekXMPP: The Sleek XMPP Library - Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout - This file is part of SleekXMPP. - - See the file LICENSE for copying permissio -""" - -import logging -from . import base -from .. xmlstream.handler.callback import Callback -from .. xmlstream.matcher.xpath import MatchXPath -from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID -from .. stanza.message import Message - - -log = logging.getLogger(__name__) - - -class ChatState(ElementBase): - namespace = 'http://jabber.org/protocol/chatstates' - plugin_attrib = 'chat_state' - interface = set(('state',)) - states = set(('active', 'composing', 'gone', 'inactive', 'paused')) - - def active(self): - self.setState('active') - - def composing(self): - self.setState('composing') - - def gone(self): - self.setState('gone') - - def inactive(self): - self.setState('inactive') - - def paused(self): - self.setState('paused') - - def setState(self, state): - if state in self.states: - self.name = state - self.xml.tag = '{%s}%s' % (self.namespace, state) - else: - raise ValueError('Invalid chat state') - - def getState(self): - return self.name - -# In order to match the various chat state elements, -# we need one stanza object per state, even though -# they are all the same except for the initial name -# value. Do not depend on the type of the chat state -# stanza object for the actual state. - -class Active(ChatState): - name = 'active' -class Composing(ChatState): - name = 'composing' -class Gone(ChatState): - name = 'gone' -class Inactive(ChatState): - name = 'inactive' -class Paused(ChatState): - name = 'paused' - - -class xep_0085(base.base_plugin): - """ - XEP-0085 Chat State Notifications - """ - - def plugin_init(self): - self.xep = '0085' - self.description = 'Chat State Notifications' - - handlers = [('Active Chat State', 'active'), - ('Composing Chat State', 'composing'), - ('Gone Chat State', 'gone'), - ('Inactive Chat State', 'inactive'), - ('Paused Chat State', 'paused')] - for handler in handlers: - self.xmpp.registerHandler( - Callback(handler[0], - MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns, - ChatState.namespace, - handler[1])), - self._handleChatState)) - - registerStanzaPlugin(Message, Active) - registerStanzaPlugin(Message, Composing) - registerStanzaPlugin(Message, Gone) - registerStanzaPlugin(Message, Inactive) - registerStanzaPlugin(Message, Paused) - - def post_init(self): - base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('http://jabber.org/protocol/chatstates') - - def _handleChatState(self, msg): - state = msg['chat_state'].name - log.debug("Chat State: %s, %s" % (state, msg['from'].jid)) - self.xmpp.event('chatstate_%s' % state, msg) diff --git a/sleekxmpp/plugins/xep_0085/__init__.py b/sleekxmpp/plugins/xep_0085/__init__.py new file mode 100644 index 0000000..ff882f0 --- /dev/null +++ b/sleekxmpp/plugins/xep_0085/__init__.py @@ -0,0 +1,10 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permissio +""" + +from sleekxmpp.plugins.xep_0085.stanza import ChatState +from sleekxmpp.plugins.xep_0085.chat_states import xep_0085 diff --git a/sleekxmpp/plugins/xep_0085/chat_states.py b/sleekxmpp/plugins/xep_0085/chat_states.py new file mode 100644 index 0000000..a2feb53 --- /dev/null +++ b/sleekxmpp/plugins/xep_0085/chat_states.py @@ -0,0 +1,48 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permissio +""" + +import logging + +import sleekxmpp +from sleekxmpp.stanza import Message +from sleekxmpp.xmlstream.handler import Callback +from sleekxmpp.xmlstream.matcher import StanzaPath +from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET +from sleekxmpp.plugins.base import base_plugin +from sleekxmpp.plugins.xep_0085 import stanza + + +log = logging.getLogger(__name__) + + +class xep_0085(base_plugin): + + """ + XEP-0085 Chat State Notifications + """ + + def plugin_init(self): + self.xep = '0085' + self.description = 'Chat State Notifications' + self.stanza = stanza + + self.xmpp.registerHandler( + Callback('Chat States', + StanzaPath('message/chat_state'), + self._handle_chat_state)) + + register_stanza_plugin(Message, stanza.ChatState) + + def post_init(self): + base_plugin.post_init(self) + self.xmpp.plugin['xep_0030'].add_feature(stanza.ChatState.namepsace) + + def _handle_chat_state(self, msg): + state = msg['chat_state'] + log.debug("Chat State: %s, %s" % (state, msg['from'].jid)) + self.xmpp.event('chatstate_%s' % state, msg) diff --git a/sleekxmpp/plugins/xep_0085/stanza.py b/sleekxmpp/plugins/xep_0085/stanza.py new file mode 100644 index 0000000..8c46758 --- /dev/null +++ b/sleekxmpp/plugins/xep_0085/stanza.py @@ -0,0 +1,73 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2011 Nathanael C. Fritz, Lance J.T. Stout + This file is part of SleekXMPP. + + See the file LICENSE for copying permissio +""" + +import sleekxmpp +from sleekxmpp.xmlstream import ElementBase, ET + + +class ChatState(ElementBase): + + """ + Example chat state stanzas: + + + + + + + + + Stanza Interfaces: + chat_state + + Attributes: + states + + Methods: + get_chat_state + set_chat_state + del_chat_state + """ + + name = '' + namespace = 'http://jabber.org/protocol/chatstates' + plugin_attrib = 'chat_state' + interfaces = set(('chat_state',)) + is_extension = True + + states = set(('active', 'composing', 'gone', 'inactive', 'paused')) + + def setup(self, xml=None): + self.xml = ET.Element('') + return True + + def get_chat_state(self): + parent = self.parent() + for state in self.states: + state_xml = parent.find('{%s}%s' % (self.namespace, state)) + if state_xml is not None: + self.xml = state_xml + return state + return '' + + def set_chat_state(self, state): + self.del_chat_state() + parent = self.parent() + if state in self.states: + self.xml = ET.Element('{%s}%s' % (self.namespace, state)) + parent.append(self.xml) + elif state not in [None, '']: + raise ValueError('Invalid chat state') + + def del_chat_state(self): + parent = self.parent() + for state in self.states: + state_xml = parent.find('{%s}%s' % (self.namespace, state)) + if state_xml is not None: + self.xml = ET.Element('') + parent.xml.remove(state_xml) diff --git a/tests/test_stanza_xep_0085.py b/tests/test_stanza_xep_0085.py index 5db7139..b08404e 100644 --- a/tests/test_stanza_xep_0085.py +++ b/tests/test_stanza_xep_0085.py @@ -4,11 +4,7 @@ import sleekxmpp.plugins.xep_0085 as xep_0085 class TestChatStates(SleekTest): def setUp(self): - register_stanza_plugin(Message, xep_0085.Active) - register_stanza_plugin(Message, xep_0085.Composing) - register_stanza_plugin(Message, xep_0085.Gone) - register_stanza_plugin(Message, xep_0085.Inactive) - register_stanza_plugin(Message, xep_0085.Paused) + register_stanza_plugin(Message, xep_0085.ChatState) def testCreateChatState(self): """Testing creating chat states.""" @@ -20,25 +16,26 @@ class TestChatStates(SleekTest): """ msg = self.Message() - msg['chat_state'].active() - self.check(msg, xmlstring % 'active', - use_values=False) - msg['chat_state'].composing() - self.check(msg, xmlstring % 'composing', - use_values=False) + self.assertEqual(msg['chat_state'], '') + self.check(msg, "", use_values=False) + msg['chat_state'] = 'active' + self.check(msg, xmlstring % 'active', use_values=False) - msg['chat_state'].gone() - self.check(msg, xmlstring % 'gone', - use_values=False) + msg['chat_state'] = 'composing' + self.check(msg, xmlstring % 'composing', use_values=False) - msg['chat_state'].inactive() - self.check(msg, xmlstring % 'inactive', - use_values=False) + msg['chat_state'] = 'gone' + self.check(msg, xmlstring % 'gone', use_values=False) - msg['chat_state'].paused() - self.check(msg, xmlstring % 'paused', - use_values=False) + msg['chat_state'] = 'inactive' + self.check(msg, xmlstring % 'inactive', use_values=False) + + msg['chat_state'] = 'paused' + self.check(msg, xmlstring % 'paused', use_values=False) + + del msg['chat_state'] + self.check(msg, "") suite = unittest.TestLoader().loadTestsFromTestCase(TestChatStates) From 1a81b2f464a3f35c99ff1659b7debb6006004d4f Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 24 Feb 2011 14:15:02 -0500 Subject: [PATCH 4/7] Add tests for XEP-0085, fix some bugs. --- sleekxmpp/plugins/xep_0085/chat_states.py | 15 +++--- tests/test_stream_xep_0085.py | 59 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) create mode 100644 tests/test_stream_xep_0085.py diff --git a/sleekxmpp/plugins/xep_0085/chat_states.py b/sleekxmpp/plugins/xep_0085/chat_states.py index a2feb53..4fb21ba 100644 --- a/sleekxmpp/plugins/xep_0085/chat_states.py +++ b/sleekxmpp/plugins/xep_0085/chat_states.py @@ -14,7 +14,7 @@ from sleekxmpp.xmlstream.handler import Callback from sleekxmpp.xmlstream.matcher import StanzaPath from sleekxmpp.xmlstream import register_stanza_plugin, ElementBase, ET from sleekxmpp.plugins.base import base_plugin -from sleekxmpp.plugins.xep_0085 import stanza +from sleekxmpp.plugins.xep_0085 import stanza, ChatState log = logging.getLogger(__name__) @@ -31,16 +31,17 @@ class xep_0085(base_plugin): self.description = 'Chat State Notifications' self.stanza = stanza - self.xmpp.registerHandler( - Callback('Chat States', - StanzaPath('message/chat_state'), - self._handle_chat_state)) + for state in ChatState.states: + self.xmpp.register_handler( + Callback('Chat State: %s' % state, + StanzaPath('message@chat_state=%s' % state), + self._handle_chat_state)) - register_stanza_plugin(Message, stanza.ChatState) + register_stanza_plugin(Message, ChatState) def post_init(self): base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature(stanza.ChatState.namepsace) + self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace) def _handle_chat_state(self, msg): state = msg['chat_state'] diff --git a/tests/test_stream_xep_0085.py b/tests/test_stream_xep_0085.py new file mode 100644 index 0000000..2a81480 --- /dev/null +++ b/tests/test_stream_xep_0085.py @@ -0,0 +1,59 @@ +import threading +import time + +from sleekxmpp.test import * + + +class TestStreamChatStates(SleekTest): + + def tearDown(self): + self.stream_close() + + def testChatStates(self): + self.stream_start(mode='client', plugins=['xep_0030', 'xep_0085']) + + results = [] + + def handle_state(msg): + results.append(msg['chat_state']) + + self.xmpp.add_event_handler('chatstate_active', handle_state) + self.xmpp.add_event_handler('chatstate_inactive', handle_state) + self.xmpp.add_event_handler('chatstate_paused', handle_state) + self.xmpp.add_event_handler('chatstate_gone', handle_state) + self.xmpp.add_event_handler('chatstate_composing', handle_state) + + self.recv(""" + + + + """) + self.recv(""" + + + + """) + self.recv(""" + + + + """) + self.recv(""" + + + + """) + self.recv(""" + + + + """) + + # Give event queue time to process + time.sleep(0.3) + expected = ['active', 'inactive', 'paused', 'composing', 'gone'] + self.failUnless(results == expected, + "Chat state event not handled: %s" % results) + + +suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamChatStates) From 45ccb313560fbfbc0354ebac9116ecb9ff963a47 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 24 Feb 2011 16:13:44 -0500 Subject: [PATCH 5/7] Remove the occasional warning about XEP-0059 not loaded. --- sleekxmpp/plugins/xep_0030/disco.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/plugins/xep_0030/disco.py b/sleekxmpp/plugins/xep_0030/disco.py index 45d6931..1c967bd 100644 --- a/sleekxmpp/plugins/xep_0030/disco.py +++ b/sleekxmpp/plugins/xep_0030/disco.py @@ -119,7 +119,7 @@ class xep_0030(base_plugin): def post_init(self): """Handle cross-plugin dependencies.""" base_plugin.post_init(self) - if self.xmpp['xep_0059']: + if 'xep_0059' in self.xmpp.plugin: register_stanza_plugin(DiscoItems, self.xmpp['xep_0059'].stanza.Set) From 5635265203712650981c479867cdcece01fa6d61 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 16 Mar 2011 06:28:12 +0800 Subject: [PATCH 6/7] Avoid infinite loop on version result We need to check if type="get". otherwise we will send our version when we will receive the version of the remote entity, and thus going in an infinite loop. --- sleekxmpp/plugins/xep_0092/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sleekxmpp/plugins/xep_0092/version.py b/sleekxmpp/plugins/xep_0092/version.py index fb3671e..46bb27f 100644 --- a/sleekxmpp/plugins/xep_0092/version.py +++ b/sleekxmpp/plugins/xep_0092/version.py @@ -42,7 +42,7 @@ class xep_0092(base_plugin): self.xmpp.register_handler( Callback('Software Version', - StanzaPath('iq/software_version'), + StanzaPath('iq@=get/software_version'), self._handle_version)) register_stanza_plugin(Iq, Version) From 6244857746cff020e02702de93270d8add64d48e Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 18 Mar 2011 15:47:21 -0400 Subject: [PATCH 7/7] Fix self.disconnect(reconnect=True) not working. --- sleekxmpp/xmlstream/xmlstream.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 18d891a..1c16556 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -353,8 +353,7 @@ class XMLStream(object): self.send_raw(self.stream_footer) # Wait for confirmation that the stream was # closed in the other direction. - if not reconnect: - self.auto_reconnect = False + self.auto_reconnect = reconnect self.stream_end_event.wait(4) if not self.auto_reconnect: self.stop.set()