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_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)
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..4fb21ba
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0085/chat_states.py
@@ -0,0 +1,49 @@
+"""
+ 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, ChatState
+
+
+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
+
+ 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, ChatState)
+
+ def post_init(self):
+ base_plugin.post_init(self)
+ self.xmpp.plugin['xep_0030'].add_feature(ChatState.namespace)
+
+ 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/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)
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
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
index a5151d7..1c16556 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.
@@ -305,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()
@@ -731,6 +778,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 +957,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 +983,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:
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)
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)