From 9c08e56ed04ff38422dfadcbf8cc18da03c3c399 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 27 Oct 2010 19:25:02 -0400 Subject: [PATCH 01/20] SSL and signal fixes. Made setting the SIG* handlers conditional on if the signal defined for the OS. Added the attribute ssl_version to XMLStream to set the version of SSL used during connection. It defaults to ssl.PROTOCOL_TLSv1, but OpenFire tends to require ssl.PROTOCOL_SSLv23. --- sleekxmpp/xmlstream/xmlstream.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index ace93cc..630e68b 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -87,6 +87,8 @@ class XMLStream(object): send_queue -- A queue of stanzas to be sent on the stream. socket -- The connection to the server. ssl_support -- Indicates if a SSL library is available for use. + ssl_version -- The version of the SSL protocol to use. + Defaults to ssl.PROTOCOL_TLSv1. state -- A state machine for managing the stream's connection state. stream_footer -- The start tag and any attributes for the stream's @@ -155,6 +157,7 @@ class XMLStream(object): self.sendXML = self.send_xml self.ssl_support = SSL_SUPPORT + self.ssl_version = ssl.PROTOCOL_TLSv1 self.state = StateMachine(('disconnected', 'connected')) self.state._set_state('disconnected') @@ -196,8 +199,11 @@ class XMLStream(object): self.auto_reconnect = True self.is_client = False - signal.signal(signal.SIGHUP, self._handle_kill) - signal.signal(signal.SIGTERM, self._handle_kill) # used in Windows + if hasattr(signal, 'SIGHUP'): + signal.signal(signal.SIGHUP, self._handle_kill) + if hasattr(signal, 'SIGTERM'): + # Used in Windows + signal.signal(signal.SIGTERM, self._handle_kill) def _handle_kill(self, signum, frame): """ @@ -370,7 +376,7 @@ class XMLStream(object): if self.ssl_support: logging.info("Negotiating TLS") ssl_socket = ssl.wrap_socket(self.socket, - ssl_version=ssl.PROTOCOL_TLSv1, + ssl_version=self.ssl_version, do_handshake_on_connect=False) if hasattr(self.socket, 'socket'): # We are using a testing socket, so preserve the top @@ -788,7 +794,7 @@ class XMLStream(object): def _threaded_event_wrapper(self, func, args): """ - Capture exceptions for event handlers that run + Capture exceptions for event handlers that run in individual threads. Arguments: From 973890e2c913c466b3dfb48c8bd01c28a7baf6c5 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 28 Oct 2010 10:42:23 -0400 Subject: [PATCH 02/20] Added try/except for setting signal handlers. Setting signal handlers from inside a thread is not supported in Python, but some applications need to run Sleek from a child thread. SleekXMPP applications that run inside a child thread will NOT be able to detect SIGHUP or SIGTERM events. Those must be caught and managed by the main program. --- sleekxmpp/xmlstream/xmlstream.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 630e68b..cc19271 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -199,11 +199,15 @@ class XMLStream(object): self.auto_reconnect = True self.is_client = False - if hasattr(signal, 'SIGHUP'): - signal.signal(signal.SIGHUP, self._handle_kill) - if hasattr(signal, 'SIGTERM'): - # Used in Windows - signal.signal(signal.SIGTERM, self._handle_kill) + try: + if hasattr(signal, 'SIGHUP'): + signal.signal(signal.SIGHUP, self._handle_kill) + if hasattr(signal, 'SIGTERM'): + # Used in Windows + signal.signal(signal.SIGTERM, self._handle_kill) + except: + logging.debug("Can not set interrupt signal handlers. " + \ + "SleekXMPP is not running from a main thread.") def _handle_kill(self, signum, frame): """ From 9e248bb8527c1903486756b4166e9e548a8eb8e2 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sun, 31 Oct 2010 18:27:52 -0400 Subject: [PATCH 03/20] Fix bug in XEP-0030 plugin. xep_0030 still referenced event_handlers. Added the method event_handled which will return the number of registered handlers for an event to resolve the issue. --- sleekxmpp/plugins/xep_0030.py | 522 +++++++++++++++---------------- sleekxmpp/xmlstream/xmlstream.py | 12 + 2 files changed, 272 insertions(+), 262 deletions(-) diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index a9d8d6a..21f4538 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -14,314 +14,312 @@ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq class DiscoInfo(ElementBase): - namespace = 'http://jabber.org/protocol/disco#info' - name = 'query' - plugin_attrib = 'disco_info' - interfaces = set(('node', 'features', 'identities')) + namespace = 'http://jabber.org/protocol/disco#info' + name = 'query' + plugin_attrib = 'disco_info' + interfaces = set(('node', 'features', 'identities')) - def getFeatures(self): - features = [] - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for feature in featuresXML: - features.append(feature.attrib['var']) - return features + def getFeatures(self): + features = [] + featuresXML = self.xml.findall('{%s}feature' % self.namespace) + for feature in featuresXML: + features.append(feature.attrib['var']) + return features - def setFeatures(self, features): - self.delFeatures() - for name in features: - self.addFeature(name) + def setFeatures(self, features): + self.delFeatures() + for name in features: + self.addFeature(name) - def delFeatures(self): - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for feature in featuresXML: - self.xml.remove(feature) + def delFeatures(self): + featuresXML = self.xml.findall('{%s}feature' % self.namespace) + for feature in featuresXML: + self.xml.remove(feature) - def addFeature(self, feature): - featureXML = ET.Element('{%s}feature' % self.namespace, - {'var': feature}) - self.xml.append(featureXML) + def addFeature(self, feature): + featureXML = ET.Element('{%s}feature' % self.namespace, + {'var': feature}) + self.xml.append(featureXML) - def delFeature(self, feature): - featuresXML = self.xml.findall('{%s}feature' % self.namespace) - for featureXML in featuresXML: - if featureXML.attrib['var'] == feature: - self.xml.remove(featureXML) + def delFeature(self, feature): + featuresXML = self.xml.findall('{%s}feature' % self.namespace) + for featureXML in featuresXML: + if featureXML.attrib['var'] == feature: + self.xml.remove(featureXML) - def getIdentities(self): - ids = [] - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - idData = (idXML.attrib['category'], - idXML.attrib['type'], - idXML.attrib.get('name', '')) - ids.append(idData) - return ids + def getIdentities(self): + ids = [] + idsXML = self.xml.findall('{%s}identity' % self.namespace) + for idXML in idsXML: + idData = (idXML.attrib['category'], + idXML.attrib['type'], + idXML.attrib.get('name', '')) + ids.append(idData) + return ids - def setIdentities(self, ids): - self.delIdentities() - for idData in ids: - self.addIdentity(*idData) + def setIdentities(self, ids): + self.delIdentities() + for idData in ids: + self.addIdentity(*idData) - def delIdentities(self): - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - self.xml.remove(idXML) + def delIdentities(self): + idsXML = self.xml.findall('{%s}identity' % self.namespace) + for idXML in idsXML: + self.xml.remove(idXML) - def addIdentity(self, category, id_type, name=''): - idXML = ET.Element('{%s}identity' % self.namespace, - {'category': category, - 'type': id_type, - 'name': name}) - self.xml.append(idXML) + def addIdentity(self, category, id_type, name=''): + idXML = ET.Element('{%s}identity' % self.namespace, + {'category': category, + 'type': id_type, + 'name': name}) + self.xml.append(idXML) - def delIdentity(self, category, id_type, name=''): - idsXML = self.xml.findall('{%s}identity' % self.namespace) - for idXML in idsXML: - idData = (idXML.attrib['category'], - idXML.attrib['type']) - delId = (category, id_type) - if idData == delId: - self.xml.remove(idXML) + def delIdentity(self, category, id_type, name=''): + idsXML = self.xml.findall('{%s}identity' % self.namespace) + for idXML in idsXML: + idData = (idXML.attrib['category'], + idXML.attrib['type']) + delId = (category, id_type) + if idData == delId: + self.xml.remove(idXML) class DiscoItems(ElementBase): - namespace = 'http://jabber.org/protocol/disco#items' - name = 'query' - plugin_attrib = 'disco_items' - interfaces = set(('node', 'items')) + namespace = 'http://jabber.org/protocol/disco#items' + name = 'query' + plugin_attrib = 'disco_items' + interfaces = set(('node', 'items')) - def getItems(self): - items = [] - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for item in itemsXML: - itemData = (item.attrib['jid'], - item.attrib.get('node'), - item.attrib.get('name')) - items.append(itemData) - return items + def getItems(self): + items = [] + itemsXML = self.xml.findall('{%s}item' % self.namespace) + for item in itemsXML: + itemData = (item.attrib['jid'], + item.attrib.get('node'), + item.attrib.get('name')) + items.append(itemData) + return items - def setItems(self, items): - self.delItems() - for item in items: - self.addItem(*item) + def setItems(self, items): + self.delItems() + for item in items: + self.addItem(*item) - def delItems(self): - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for item in itemsXML: - self.xml.remove(item) + def delItems(self): + itemsXML = self.xml.findall('{%s}item' % self.namespace) + for item in itemsXML: + self.xml.remove(item) - def addItem(self, jid, node='', name=''): - itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid}) - if name: - itemXML.attrib['name'] = name - if node: - itemXML.attrib['node'] = node - self.xml.append(itemXML) + def addItem(self, jid, node='', name=''): + itemXML = ET.Element('{%s}item' % self.namespace, {'jid': jid}) + if name: + itemXML.attrib['name'] = name + if node: + itemXML.attrib['node'] = node + self.xml.append(itemXML) + + def delItem(self, jid, node=''): + itemsXML = self.xml.findall('{%s}item' % self.namespace) + for itemXML in itemsXML: + itemData = (itemXML.attrib['jid'], + itemXML.attrib.get('node', '')) + itemDel = (jid, node) + if itemData == itemDel: + self.xml.remove(itemXML) - def delItem(self, jid, node=''): - itemsXML = self.xml.findall('{%s}item' % self.namespace) - for itemXML in itemsXML: - itemData = (itemXML.attrib['jid'], - itemXML.attrib.get('node', '')) - itemDel = (jid, node) - if itemData == itemDel: - self.xml.remove(itemXML) - class DiscoNode(object): - """ - Collection object for grouping info and item information - into nodes. - """ - def __init__(self, name): - self.name = name - self.info = DiscoInfo() - self.items = DiscoItems() + """ + Collection object for grouping info and item information + into nodes. + """ + def __init__(self, name): + self.name = name + self.info = DiscoInfo() + self.items = DiscoItems() - self.info['node'] = name - self.items['node'] = name + self.info['node'] = name + self.items['node'] = name - # This is a bit like poor man's inheritance, but - # to simplify adding information to the node we - # map node functions to either the info or items - # stanza objects. - # - # We don't want to make DiscoNode inherit from - # DiscoInfo and DiscoItems because DiscoNode is - # not an actual stanza, and doing so would create - # confusion and potential bugs. + # This is a bit like poor man's inheritance, but + # to simplify adding information to the node we + # map node functions to either the info or items + # stanza objects. + # + # We don't want to make DiscoNode inherit from + # DiscoInfo and DiscoItems because DiscoNode is + # not an actual stanza, and doing so would create + # confusion and potential bugs. - self._map(self.items, 'items', ['get', 'set', 'del']) - self._map(self.items, 'item', ['add', 'del']) - self._map(self.info, 'identities', ['get', 'set', 'del']) - self._map(self.info, 'identity', ['add', 'del']) - self._map(self.info, 'features', ['get', 'set', 'del']) - self._map(self.info, 'feature', ['add', 'del']) + self._map(self.items, 'items', ['get', 'set', 'del']) + self._map(self.items, 'item', ['add', 'del']) + self._map(self.info, 'identities', ['get', 'set', 'del']) + self._map(self.info, 'identity', ['add', 'del']) + self._map(self.info, 'features', ['get', 'set', 'del']) + self._map(self.info, 'feature', ['add', 'del']) - def isEmpty(self): - """ - Test if the node contains any information. Useful for - determining if a node can be deleted. - """ - ids = self.getIdentities() - features = self.getFeatures() - items = self.getItems() + def isEmpty(self): + """ + Test if the node contains any information. Useful for + determining if a node can be deleted. + """ + ids = self.getIdentities() + features = self.getFeatures() + items = self.getItems() - if not ids and not features and not items: - return True - return False + if not ids and not features and not items: + return True + return False - def _map(self, obj, interface, access): - """ - Map functions of the form obj.accessInterface - to self.accessInterface for each given access type. - """ - interface = interface.title() - for access_type in access: - method = access_type + interface - if hasattr(obj, method): - setattr(self, method, getattr(obj, method)) + def _map(self, obj, interface, access): + """ + Map functions of the form obj.accessInterface + to self.accessInterface for each given access type. + """ + interface = interface.title() + for access_type in access: + method = access_type + interface + if hasattr(obj, method): + setattr(self, method, getattr(obj, method)) class xep_0030(base.base_plugin): - """ - XEP-0030 Service Discovery - """ - - def plugin_init(self): - self.xep = '0030' - self.description = 'Service Discovery' + """ + XEP-0030 Service Discovery + """ - self.xmpp.registerHandler( - Callback('Disco Items', - MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, - DiscoItems.namespace)), - self.handle_item_query)) + def plugin_init(self): + self.xep = '0030' + self.description = 'Service Discovery' - self.xmpp.registerHandler( - Callback('Disco Info', - MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, - DiscoInfo.namespace)), - self.handle_info_query)) + self.xmpp.registerHandler( + Callback('Disco Items', + MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, + DiscoItems.namespace)), + self.handle_item_query)) - registerStanzaPlugin(Iq, DiscoInfo) - registerStanzaPlugin(Iq, DiscoItems) + self.xmpp.registerHandler( + Callback('Disco Info', + MatchXPath('{%s}iq/{%s}query' % (self.xmpp.default_ns, + DiscoInfo.namespace)), + self.handle_info_query)) - self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) - self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) + registerStanzaPlugin(Iq, DiscoInfo) + registerStanzaPlugin(Iq, DiscoItems) - self.nodes = {'main': DiscoNode('main')} + self.xmpp.add_event_handler('disco_items_request', self.handle_disco_items) + self.xmpp.add_event_handler('disco_info_request', self.handle_disco_info) - def add_node(self, node): - if node not in self.nodes: - self.nodes[node] = DiscoNode(node) + self.nodes = {'main': DiscoNode('main')} - def del_node(self, node): - if node in self.nodes: - del self.nodes[node] + def add_node(self, node): + if node not in self.nodes: + self.nodes[node] = DiscoNode(node) - def handle_item_query(self, iq): - if iq['type'] == 'get': - logging.debug("Items requested by %s" % iq['from']) - self.xmpp.event('disco_items_request', iq) - elif iq['type'] == 'result': - logging.debug("Items result from %s" % iq['from']) - self.xmpp.event('disco_items', iq) + def del_node(self, node): + if node in self.nodes: + del self.nodes[node] - def handle_info_query(self, iq): - if iq['type'] == 'get': - logging.debug("Info requested by %s" % iq['from']) - self.xmpp.event('disco_info_request', iq) - elif iq['type'] == 'result': - logging.debug("Info result from %s" % iq['from']) - self.xmpp.event('disco_info', iq) + def handle_item_query(self, iq): + if iq['type'] == 'get': + logging.debug("Items requested by %s" % iq['from']) + self.xmpp.event('disco_items_request', iq) + elif iq['type'] == 'result': + logging.debug("Items result from %s" % iq['from']) + self.xmpp.event('disco_items', iq) - def handle_disco_info(self, iq, forwarded=False): - """ - A default handler for disco#info requests. If another - handler is registered, this one will defer and not run. - """ - handlers = self.xmpp.event_handlers['disco_info_request'] - if not forwarded and len(handlers) > 1: - return + def handle_info_query(self, iq): + if iq['type'] == 'get': + logging.debug("Info requested by %s" % iq['from']) + self.xmpp.event('disco_info_request', iq) + elif iq['type'] == 'result': + logging.debug("Info result from %s" % iq['from']) + self.xmpp.event('disco_info', iq) - node_name = iq['disco_info']['node'] - if not node_name: - node_name = 'main' + def handle_disco_info(self, iq, forwarded=False): + """ + A default handler for disco#info requests. If another + handler is registered, this one will defer and not run. + """ + if not forwarded and self.xmpp.event_handled('disco_info_request'): + return - logging.debug("Using default handler for disco#info on node '%s'." % node_name) + node_name = iq['disco_info']['node'] + if not node_name: + node_name = 'main' - if node_name in self.nodes: - node = self.nodes[node_name] - iq.reply().setPayload(node.info.xml).send() - else: - logging.debug("Node %s requested, but does not exist." % node_name) - iq.reply().error().setPayload(iq['disco_info'].xml) - iq['error']['code'] = '404' - iq['error']['type'] = 'cancel' - iq['error']['condition'] = 'item-not-found' - iq.send() - - def handle_disco_items(self, iq, forwarded=False): - """ - A default handler for disco#items requests. If another - handler is registered, this one will defer and not run. + logging.debug("Using default handler for disco#info on node '%s'." % node_name) - If this handler is called by your own custom handler with - forwarded set to True, then it will run as normal. - """ - handlers = self.xmpp.event_handlers['disco_items_request'] - if not forwarded and len(handlers) > 1: - return + if node_name in self.nodes: + node = self.nodes[node_name] + iq.reply().setPayload(node.info.xml).send() + else: + logging.debug("Node %s requested, but does not exist." % node_name) + iq.reply().error().setPayload(iq['disco_info'].xml) + iq['error']['code'] = '404' + iq['error']['type'] = 'cancel' + iq['error']['condition'] = 'item-not-found' + iq.send() - node_name = iq['disco_items']['node'] - if not node_name: - node_name = 'main' + def handle_disco_items(self, iq, forwarded=False): + """ + A default handler for disco#items requests. If another + handler is registered, this one will defer and not run. - logging.debug("Using default handler for disco#items on node '%s'." % node_name) + If this handler is called by your own custom handler with + forwarded set to True, then it will run as normal. + """ + if not forwarded and self.xmpp.event_handled('disco_items_request'): + return - if node_name in self.nodes: - node = self.nodes[node_name] - iq.reply().setPayload(node.items.xml).send() - else: - logging.debug("Node %s requested, but does not exist." % node_name) - iq.reply().error().setPayload(iq['disco_items'].xml) - iq['error']['code'] = '404' - iq['error']['type'] = 'cancel' - iq['error']['condition'] = 'item-not-found' - iq.send() + node_name = iq['disco_items']['node'] + if not node_name: + node_name = 'main' - # Older interface methods for backwards compatibility + logging.debug("Using default handler for disco#items on node '%s'." % node_name) - def getInfo(self, jid, node='', dfrom=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = jid - iq['from'] = dfrom - iq['disco_info']['node'] = node - return iq.send() + if node_name in self.nodes: + node = self.nodes[node_name] + iq.reply().setPayload(node.items.xml).send() + else: + logging.debug("Node %s requested, but does not exist." % node_name) + iq.reply().error().setPayload(iq['disco_items'].xml) + iq['error']['code'] = '404' + iq['error']['type'] = 'cancel' + iq['error']['condition'] = 'item-not-found' + iq.send() - def getItems(self, jid, node='', dfrom=None): - iq = self.xmpp.Iq() - iq['type'] = 'get' - iq['to'] = jid - iq['from'] = dfrom - iq['disco_items']['node'] = node - return iq.send() - - def add_feature(self, feature, node='main'): - self.add_node(node) - self.nodes[node].addFeature(feature) - - def add_identity(self, category='', itype='', name='', node='main'): - self.add_node(node) - self.nodes[node].addIdentity(category=category, - id_type=itype, - name=name) - - def add_item(self, jid=None, name='', node='main', subnode=''): - self.add_node(node) - self.add_node(subnode) - if jid is None: - jid = self.xmpp.fulljid - self.nodes[node].addItem(jid=jid, name=name, node=subnode) + # Older interface methods for backwards compatibility + + def getInfo(self, jid, node='', dfrom=None): + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq['to'] = jid + iq['from'] = dfrom + iq['disco_info']['node'] = node + return iq.send() + + def getItems(self, jid, node='', dfrom=None): + iq = self.xmpp.Iq() + iq['type'] = 'get' + iq['to'] = jid + iq['from'] = dfrom + iq['disco_items']['node'] = node + return iq.send() + + def add_feature(self, feature, node='main'): + self.add_node(node) + self.nodes[node].addFeature(feature) + + def add_identity(self, category='', itype='', name='', node='main'): + self.add_node(node) + self.nodes[node].addIdentity(category=category, + id_type=itype, + name=name) + + def add_item(self, jid=None, name='', node='main', subnode=''): + self.add_node(node) + self.add_node(subnode) + if jid is None: + jid = self.xmpp.fulljid + self.nodes[node].addItem(jid=jid, name=name, node=subnode) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index cc19271..546654d 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -379,6 +379,7 @@ class XMLStream(object): """ if self.ssl_support: logging.info("Negotiating TLS") + logging.info("Using SSL version: %s" % str(self.ssl_version)) ssl_socket = ssl.wrap_socket(self.socket, ssl_version=self.ssl_version, do_handshake_on_connect=False) @@ -527,6 +528,17 @@ class XMLStream(object): self.__event_handlers[name] = filter(filter_pointers, self.__event_handlers[name]) + def event_handled(self, name): + """ + Indicates if an event has any associated handlers. + + Returns the number of registered handlers. + + Arguments: + name -- The name of the event to check. + """ + return len(self.__event_handlers.get(name, [])) + def event(self, name, data={}, direct=False): """ Manually trigger a custom event. From ffc6f031d9134015f16fadedc4e94e6f1a502b7b Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 3 Nov 2010 12:37:26 -0400 Subject: [PATCH 04/20] Updated namespaced used in the XEP-0199 plugin. --- sleekxmpp/plugins/xep_0199.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index 3fc62f5..ccd284f 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -16,14 +16,14 @@ class xep_0199(base.base_plugin): def plugin_init(self): self.description = "XMPP Ping" self.xep = "0199" - self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') + self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') self.running = False #if self.config.get('keepalive', True): #self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) def post_init(self): base.base_plugin.post_init(self) - self.xmpp.plugin['xep_0030'].add_feature('http://www.xmpp.org/extensions/xep-0199.html#ns') + self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:ping') def handler_pingserver(self, xml): if not self.running: @@ -47,7 +47,7 @@ class xep_0199(base.base_plugin): iq = self.xmpp.makeIq(id) iq.attrib['type'] = 'get' iq.attrib['to'] = jid - ping = ET.Element('{http://www.xmpp.org/extensions/xep-0199.html#ns}ping') + ping = ET.Element('{urn:xmpp:ping}ping') iq.append(ping) startTime = time.clock() #pingresult = self.xmpp.send(iq, self.xmpp.makeIq(id), timeout) From 0214db75451627c4e0d666e8567db24da31e4056 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 3 Nov 2010 12:38:13 -0400 Subject: [PATCH 05/20] Catch exceptions for direct events. Events triggered with direct=True will have exceptions caught. Note that all event handlers in a direct event will currently run in the same thread. --- sleekxmpp/xmlstream/xmlstream.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index cc19271..d47557b 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -535,13 +535,22 @@ class XMLStream(object): name -- The name of the event to trigger. data -- Data that will be passed to each event handler. Defaults to an empty dictionary. - direct -- Runs the event directly if True. + direct -- Runs the event directly if True, skipping the + event queue. All event handlers will run in the + same thread. """ for handler in self.__event_handlers.get(name, []): if direct: - handler[0](copy.copy(data)) + try: + handler[0](copy.copy(data)) + except Exception as e: + error_msg = 'Error processing event handler: %s' + logging.exception(error_msg % str(handler[0])) + if hasattr(data, 'exception'): + data.exception(e) else: self.event_queue.put(('event', handler, copy.copy(data))) + if handler[2]: # If the handler is disposable, we will go ahead and # remove it now instead of waiting for it to be @@ -807,11 +816,6 @@ class XMLStream(object): """ try: func(*args) - except Exception as e: - error_msg = 'Error processing event handler: %s' - logging.exception(error_msg % str(func)) - if hasattr(args[0], 'exception'): - args[0].exception(e) def _event_runner(self): """ From 1bf34caa5b0854978596b32e2458f3c0df4552dd Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 3 Nov 2010 14:04:18 -0400 Subject: [PATCH 06/20] Fixes for XEP-0199 plugin. Quick fixes to get the XEP-0199 plugin working until a proper cleanup is done. --- sleekxmpp/plugins/xep_0199.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index ccd284f..a78fd5f 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -18,8 +18,8 @@ class xep_0199(base.base_plugin): self.xep = "0199" self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') self.running = False - #if self.config.get('keepalive', True): - #self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) + if self.config.get('keepalive', True): + self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) def post_init(self): base.base_plugin.post_init(self) @@ -35,7 +35,7 @@ class xep_0199(base.base_plugin): def handler_ping(self, xml): iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) - iq.attrib['to'] = xml.get('from', self.xmpp.server) + iq.attrib['to'] = xml.get('from', self.xmpp.boundjid.domain) self.xmpp.send(iq) def sendPing(self, jid, timeout = 30): From 38c2f51f834d8f17ebdcca45ec795fb935f969b5 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Thu, 4 Nov 2010 11:39:41 -0700 Subject: [PATCH 07/20] fixed indent errors --- sleekxmpp/plugins/xep_0199.py | 82 ++++++++++++++++---------------- sleekxmpp/xmlstream/xmlstream.py | 2 + 2 files changed, 43 insertions(+), 41 deletions(-) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index a78fd5f..1be326c 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -11,49 +11,49 @@ import time import logging class xep_0199(base.base_plugin): - """XEP-0199 XMPP Ping""" + """XEP-0199 XMPP Ping""" - def plugin_init(self): - self.description = "XMPP Ping" - self.xep = "0199" + def plugin_init(self): + self.description = "XMPP Ping" + self.xep = "0199" self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') - self.running = False - if self.config.get('keepalive', True): - self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) - - def post_init(self): - base.base_plugin.post_init(self) + self.running = False + if self.config.get('keepalive', True): + self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) + + def post_init(self): + base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:ping') - - def handler_pingserver(self, xml): - if not self.running: - time.sleep(self.config.get('frequency', 300)) - while self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is not False: - time.sleep(self.config.get('frequency', 300)) - logging.debug("Did not recieve ping back in time. Requesting Reconnect.") - self.xmpp.disconnect(reconnect=True) - - def handler_ping(self, xml): - iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) - iq.attrib['to'] = xml.get('from', self.xmpp.boundjid.domain) - self.xmpp.send(iq) + + def handler_pingserver(self, xml): + if not self.running: + time.sleep(self.config.get('frequency', 300)) + while self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is not False: + time.sleep(self.config.get('frequency', 300)) + logging.debug("Did not recieve ping back in time. Requesting Reconnect.") + self.xmpp.disconnect(reconnect=True) + + def handler_ping(self, xml): + iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) + iq.attrib['to'] = xml.get('from', self.xmpp.boundjid.domain) + self.xmpp.send(iq) - def sendPing(self, jid, timeout = 30): - """ sendPing(jid, timeout) - Sends a ping to the specified jid, returning the time (in seconds) - to receive a reply, or None if no reply is received in timeout seconds. - """ - id = self.xmpp.getNewId() - iq = self.xmpp.makeIq(id) - iq.attrib['type'] = 'get' - iq.attrib['to'] = jid + def sendPing(self, jid, timeout = 30): + """ sendPing(jid, timeout) + Sends a ping to the specified jid, returning the time (in seconds) + to receive a reply, or None if no reply is received in timeout seconds. + """ + id = self.xmpp.getNewId() + iq = self.xmpp.makeIq(id) + iq.attrib['type'] = 'get' + iq.attrib['to'] = jid ping = ET.Element('{urn:xmpp:ping}ping') - iq.append(ping) - startTime = time.clock() - #pingresult = self.xmpp.send(iq, self.xmpp.makeIq(id), timeout) - pingresult = iq.send() - endTime = time.clock() - if pingresult == False: - #self.xmpp.disconnect(reconnect=True) - return False - return endTime - startTime + iq.append(ping) + startTime = time.clock() + #pingresult = self.xmpp.send(iq, self.xmpp.makeIq(id), timeout) + pingresult = iq.send() + endTime = time.clock() + if pingresult == False: + #self.xmpp.disconnect(reconnect=True) + return False + return endTime - startTime diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 1641271..218b4b5 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -828,6 +828,8 @@ class XMLStream(object): """ try: func(*args) + except: + pass def _event_runner(self): """ From 7351fe1a0226298419e3d174d7b156daad91c131 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Thu, 4 Nov 2010 14:35:35 -0400 Subject: [PATCH 08/20] Fix bug introduced while fixing another bug. Threaded event handlers now handle exceptions again. --- sleekxmpp/xmlstream/xmlstream.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 218b4b5..a526281 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -828,8 +828,11 @@ class XMLStream(object): """ try: func(*args) - except: - pass + except Exception as e: + error_msg = 'Error processing event handler: %s' + logging.exception(error_msg % str(func)) + if hasattr(args[0], 'exception'): + args[0].exception(e) def _event_runner(self): """ From d0c506f93010f62cd447ce29f98ab991f521ec99 Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Fri, 5 Nov 2010 14:45:58 -0400 Subject: [PATCH 09/20] Simplified SleekTest. * check_stanza does not require stanza_class parameter. Introspection! * check_message, check_iq, and check_presence removed -- use check instead. * stream_send_stanza, stream_send_message, stream_send_iq, and stream_send_presence removed -- use send instead. * Use recv instead of recv_message, recv_presence, etc. * check_jid instead of check_JID * stream_start may accept multi=True to return a new SleekTest instance for testing multiple streams at once. --- sleekxmpp/plugins/__init__.py | 4 +- sleekxmpp/test/sleektest.py | 281 ++++++++----------------------- sleekxmpp/xmlstream/xmlstream.py | 22 +++ tests/live_test.py | 32 ++-- tests/test_jid.py | 22 +-- tests/test_stanza_element.py | 36 ++-- tests/test_stanza_error.py | 10 +- tests/test_stanza_gmail.py | 2 +- tests/test_stanza_iq.py | 16 +- tests/test_stanza_message.py | 4 +- tests/test_stanza_presence.py | 10 +- tests/test_stanza_roster.py | 4 +- tests/test_stanza_xep_0004.py | 8 +- tests/test_stanza_xep_0030.py | 14 +- tests/test_stanza_xep_0033.py | 18 +- tests/test_stanza_xep_0060.py | 38 ++--- tests/test_stanza_xep_0085.py | 10 +- tests/test_stream.py | 10 +- tests/test_stream_exceptions.py | 12 +- tests/test_stream_handlers.py | 16 +- tests/test_stream_presence.py | 20 +-- tests/test_stream_roster.py | 8 +- 22 files changed, 242 insertions(+), 355 deletions(-) diff --git a/sleekxmpp/plugins/__init__.py b/sleekxmpp/plugins/__init__.py index 4dcec8f..427ab04 100644 --- a/sleekxmpp/plugins/__init__.py +++ b/sleekxmpp/plugins/__init__.py @@ -6,5 +6,5 @@ See the file LICENSE for copying permission. """ __all__ = ['xep_0004', 'xep_0012', 'xep_0030', 'xep_0033', 'xep_0045', - 'xep_0050', 'xep_0078', 'xep_0085', 'xep_0092', 'xep_0199', - 'gmail_notify', 'xep_0060', 'xep_0202'] + 'xep_0050', 'xep_0085', 'xep_0092', 'xep_0199', 'gmail_notify', + 'xep_0060', 'xep_0202'] diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index 2901e59..f8b4b54 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -1,5 +1,4 @@ """ - SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz, Lance J.T. Stout This file is part of SleekXMPP. @@ -27,27 +26,29 @@ class SleekTest(unittest.TestCase): Message -- Create a Message stanza object. Iq -- Create an Iq stanza object. Presence -- Create a Presence stanza object. - check_stanza -- Compare a generic stanza against an XML string. - check_message -- Compare a Message stanza against an XML string. - check_iq -- Compare an Iq stanza against an XML string. - check_presence -- Compare a Presence stanza against an XML string. + check_jid -- Check a JID and its component parts. + check -- Compare a stanza against an XML string. stream_start -- Initialize a dummy XMPP client. - stream_recv -- Queue data for XMPP client to receive. - stream_make_header -- Create a stream header. - stream_send_header -- Check that the given header has been sent. - stream_send_message -- Check that the XMPP client sent the given - Message stanza. - stream_send_iq -- Check that the XMPP client sent the given - Iq stanza. - stream_send_presence -- Check thatt the XMPP client sent the given - Presence stanza. - stream_send_stanza -- Check that the XMPP client sent the given - generic stanza. stream_close -- Disconnect the XMPP client. + make_header -- Create a stream header. + send_header -- Check that the given header has been sent. + send_feature -- Send a raw XML element. + send -- Check that the XMPP client sent the given + generic stanza. + recv -- Queue data for XMPP client to receive, or + verify the data that was received from a + live connection. + recv_header -- Check that a given stream header + was received. + recv_feature -- Check that a given, raw XML element + was recveived. fix_namespaces -- Add top-level namespace to an XML object. compare -- Compare XML objects against each other. """ + def runTest(self): + pass + def parse_xml(self, xml_string): try: xml = ET.fromstring(xml_string) @@ -103,10 +104,8 @@ class SleekTest(unittest.TestCase): """ return Presence(None, *args, **kwargs) - - - def check_JID(self, jid, user=None, domain=None, resource=None, - bare=None, full=None, string=None): + def check_jid(self, jid, user=None, domain=None, resource=None, + bare=None, full=None, string=None): """ Verify the components of a JID. @@ -141,8 +140,8 @@ class SleekTest(unittest.TestCase): # ------------------------------------------------------------------ # Methods for comparing stanza objects to XML strings - def check_stanza(self, stanza_class, stanza, xml_string, - defaults=None, use_values=True): + def check(self, stanza, xml_string, + defaults=None, use_values=True): """ Create and compare several stanza objects to a correct XML string. @@ -161,7 +160,6 @@ class SleekTest(unittest.TestCase): must take into account any extra elements that are included by default. Arguments: - stanza_class -- The class of the stanza being tested. stanza -- The stanza object to test. xml_string -- A string version of the correct XML expected. defaults -- A list of stanza interfaces that have default @@ -172,6 +170,7 @@ class SleekTest(unittest.TestCase): setStanzaValues() should be used. Defaults to True. """ + stanza_class = stanza.__class__ xml = self.parse_xml(xml_string) # Ensure that top level namespaces are used, even if they @@ -188,7 +187,11 @@ class SleekTest(unittest.TestCase): # so that they will compare correctly. default_stanza = stanza_class() if defaults is None: - defaults = [] + known_defaults = { + Message: ['type'], + Presence: ['priority'] + } + defaults = known_defaults.get(stanza_class, []) for interface in defaults: stanza[interface] = stanza[interface] stanza2[interface] = stanza2[interface] @@ -219,62 +222,6 @@ class SleekTest(unittest.TestCase): self.failUnless(result, debug) - def check_message(self, msg, xml_string, use_values=True): - """ - Create and compare several message stanza objects to a - correct XML string. - - If use_values is False, the test using getStanzaValues() and - setStanzaValues() will not be used. - - Arguments: - msg -- The Message stanza object to check. - xml_string -- The XML contents to compare against. - use_values -- Indicates if the test using getStanzaValues - and setStanzaValues should be used. Defaults - to True. - """ - - return self.check_stanza(Message, msg, xml_string, - defaults=['type'], - use_values=use_values) - - def check_iq(self, iq, xml_string, use_values=True): - """ - Create and compare several iq stanza objects to a - correct XML string. - - If use_values is False, the test using getStanzaValues() and - setStanzaValues() will not be used. - - Arguments: - iq -- The Iq stanza object to check. - xml_string -- The XML contents to compare against. - use_values -- Indicates if the test using getStanzaValues - and setStanzaValues should be used. Defaults - to True. - """ - return self.check_stanza(Iq, iq, xml_string, use_values=use_values) - - def check_presence(self, pres, xml_string, use_values=True): - """ - Create and compare several presence stanza objects to a - correct XML string. - - If use_values is False, the test using getStanzaValues() and - setStanzaValues() will not be used. - - Arguments: - iq -- The Iq stanza object to check. - xml_string -- The XML contents to compare against. - use_values -- Indicates if the test using getStanzaValues - and setStanzaValues should be used. Defaults - to True. - """ - return self.check_stanza(Presence, pres, xml_string, - defaults=['priority'], - use_values=use_values) - # ------------------------------------------------------------------ # Methods for simulating stanza streams. @@ -302,7 +249,6 @@ class SleekTest(unittest.TestCase): port -- The port to use when connecting to the server. Defaults to 5222. """ - if mode == 'client': self.xmpp = ClientXMPP(jid, password) elif mode == 'component': @@ -337,13 +283,13 @@ class SleekTest(unittest.TestCase): if mode == 'component': self.xmpp.socket.next_sent(timeout=1) - def stream_make_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - version="1.0", - xml_header=True): + def make_header(self, sto='', + sfrom='', + sid='', + stream_ns="http://etherx.jabber.org/streams", + default_ns="jabber:client", + version="1.0", + xml_header=True): """ Create a stream header to be received by the test XMPP agent. @@ -374,8 +320,8 @@ class SleekTest(unittest.TestCase): parts.append('xmlns="%s"' % default_ns) return header % ' '.join(parts) - def stream_recv(self, data, stanza_class=StanzaBase, defaults=[], - use_values=True, timeout=1): + def recv(self, data, stanza_class=StanzaBase, defaults=[], + use_values=True, timeout=1): """ Pass data to the dummy XMPP client as if it came from an XMPP server. @@ -402,7 +348,7 @@ class SleekTest(unittest.TestCase): if recv_data is None: return False stanza = stanza_class(xml=self.parse_xml(recv_data)) - return self.check_stanza(stanza_class, stanza, data, + return self.check(stanza_class, stanza, data, defaults=defaults, use_values=use_values) else: @@ -410,14 +356,14 @@ class SleekTest(unittest.TestCase): data = str(data) self.xmpp.socket.recv_data(data) - def stream_recv_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - version="1.0", - xml_header=False, - timeout=1): + def recv_header(self, sto='', + sfrom='', + sid='', + stream_ns="http://etherx.jabber.org/streams", + default_ns="jabber:client", + version="1.0", + xml_header=False, + timeout=1): """ Check that a given stream header was received. @@ -433,11 +379,11 @@ class SleekTest(unittest.TestCase): timeout -- Length of time to wait in seconds for a response. """ - header = self.stream_make_header(sto, sfrom, sid, - stream_ns=stream_ns, - default_ns=default_ns, - version=version, - xml_header=xml_header) + header = self.make_header(sto, sfrom, sid, + stream_ns=stream_ns, + default_ns=default_ns, + version=version, + xml_header=xml_header) recv_header = self.xmpp.socket.next_recv(timeout) if recv_header is None: raise ValueError("Socket did not return data.") @@ -477,9 +423,8 @@ class SleekTest(unittest.TestCase): "Stream headers do not match:\nDesired:\n%s\nReceived:\n%s" % ( '%s %s' % (xml.tag, xml.attrib), '%s %s' % (recv_xml.tag, recv_xml.attrib))) - #tostring(xml), tostring(recv_xml)))#recv_header)) - def stream_recv_feature(self, data, use_values=True, timeout=1): + def recv_feature(self, data, use_values=True, timeout=1): """ """ if self.xmpp.socket.is_live: @@ -499,39 +444,14 @@ class SleekTest(unittest.TestCase): data = str(data) self.xmpp.socket.recv_data(data) - - - def stream_recv_message(self, data, use_values=True, timeout=1): - """ - """ - return self.stream_recv(data, stanza_class=Message, - defaults=['type'], - use_values=use_values, - timeout=timeout) - - def stream_recv_iq(self, data, use_values=True, timeout=1): - """ - """ - return self.stream_recv(data, stanza_class=Iq, - use_values=use_values, - timeout=timeout) - - def stream_recv_presence(self, data, use_values=True, timeout=1): - """ - """ - return self.stream_recv(data, stanza_class=Presence, - defaults=['priority'], - use_values=use_values, - timeout=timeout) - - def stream_send_header(self, sto='', - sfrom='', - sid='', - stream_ns="http://etherx.jabber.org/streams", - default_ns="jabber:client", - version="1.0", - xml_header=False, - timeout=1): + def send_header(self, sto='', + sfrom='', + sid='', + stream_ns="http://etherx.jabber.org/streams", + default_ns="jabber:client", + version="1.0", + xml_header=False, + timeout=1): """ Check that a given stream header was sent. @@ -547,11 +467,11 @@ class SleekTest(unittest.TestCase): timeout -- Length of time to wait in seconds for a response. """ - header = self.stream_make_header(sto, sfrom, sid, - stream_ns=stream_ns, - default_ns=default_ns, - version=version, - xml_header=xml_header) + header = self.make_header(sto, sfrom, sid, + stream_ns=stream_ns, + default_ns=default_ns, + version=version, + xml_header=xml_header) sent_header = self.xmpp.socket.next_sent(timeout) if sent_header is None: raise ValueError("Socket did not return data.") @@ -569,7 +489,7 @@ class SleekTest(unittest.TestCase): "Stream headers do not match:\nDesired:\n%s\nSent:\n%s" % ( header, sent_header)) - def stream_send_feature(self, data, use_values=True, timeout=1): + def send_feature(self, data, use_values=True, timeout=1): """ """ sent_data = self.xmpp.socket.next_sent(timeout) @@ -581,13 +501,13 @@ class SleekTest(unittest.TestCase): "Features do not match.\nDesired:\n%s\nSent:\n%s" % ( tostring(xml), tostring(sent_xml))) - def stream_send_stanza(self, stanza_class, data, defaults=None, - use_values=True, timeout=.1): + def send(self, data, defaults=None, + use_values=True, timeout=.1): """ Check that the XMPP client sent the given stanza XML. Extracts the next sent stanza and compares it with the given - XML using check_stanza. + XML using check. Arguments: stanza_class -- The class of the sent stanza object. @@ -599,70 +519,15 @@ class SleekTest(unittest.TestCase): timeout -- Time in seconds to wait for a stanza before failing the check. """ - if isintance(data, str): - data = stanza_class(xml=self.parse_xml(data)) + if isinstance(data, str): + xml = self.parse_xml(data) + self.fix_namespaces(xml, 'jabber:client') + data = self.xmpp._build_stanza(xml, 'jabber:client') sent = self.xmpp.socket.next_sent(timeout) - self.check_stanza(stanza_class, data, sent, + self.check(data, sent, defaults=defaults, use_values=use_values) - def stream_send_message(self, data, use_values=True, timeout=.1): - """ - Check that the XMPP client sent the given stanza XML. - - Extracts the next sent stanza and compares it with the given - XML using check_message. - - Arguments: - data -- The XML string of the expected Message stanza, - or an equivalent stanza object. - use_values -- Modifies the type of tests used by check_message. - timeout -- Time in seconds to wait for a stanza before - failing the check. - """ - if isinstance(data, str): - data = self.Message(xml=self.parse_xml(data)) - sent = self.xmpp.socket.next_sent(timeout) - self.check_message(data, sent, use_values) - - def stream_send_iq(self, data, use_values=True, timeout=.1): - """ - Check that the XMPP client sent the given stanza XML. - - Extracts the next sent stanza and compares it with the given - XML using check_iq. - - Arguments: - data -- The XML string of the expected Iq stanza, - or an equivalent stanza object. - use_values -- Modifies the type of tests used by check_iq. - timeout -- Time in seconds to wait for a stanza before - failing the check. - """ - if isinstance(data, str): - data = self.Iq(xml=self.parse_xml(data)) - sent = self.xmpp.socket.next_sent(timeout) - self.check_iq(data, sent, use_values) - - def stream_send_presence(self, data, use_values=True, timeout=.1): - """ - Check that the XMPP client sent the given stanza XML. - - Extracts the next sent stanza and compares it with the given - XML using check_presence. - - Arguments: - data -- The XML string of the expected Presence stanza, - or an equivalent stanza object. - use_values -- Modifies the type of tests used by check_presence. - timeout -- Time in seconds to wait for a stanza before - failing the check. - """ - if isinstance(data, str): - data = self.Presence(xml=self.parse_xml(data)) - sent = self.xmpp.socket.next_sent(timeout) - self.check_presence(data, sent, use_values) - def stream_close(self): """ Disconnect the dummy XMPP client. diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index a526281..85619db 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -772,6 +772,28 @@ class XMLStream(object): root.clear() logging.debug("Ending read XML loop") + def _build_stanza(self, xml, default_ns=None): + """ + Create a stanza object from a given XML object. + + If a specialized stanza type is not found for the XML, then + a generic StanzaBase stanza will be returned. + + Arguments: + xml -- The XML object to convert into a stanza object. + default_ns -- Optional default namespace to use instead of the + stream's current default namespace. + """ + if default_ns is None: + default_ns = self.default_ns + stanza_type = StanzaBase + for stanza_class in self.__root_stanza: + if xml.tag == "{%s}%s" % (default_ns, stanza_class.name): + stanza_type = stanza_class + break + stanza = stanza_type(self, xml) + return stanza + def __spawn_event(self, xml): """ Analyze incoming XML stanzas and convert them into stanza diff --git a/tests/live_test.py b/tests/live_test.py index 7e8945f..4b4394e 100644 --- a/tests/live_test.py +++ b/tests/live_test.py @@ -20,9 +20,9 @@ class TestLiveStream(SleekTest): # Use sid=None to ignore any id sent by the server since # we can't know it in advance. - self.stream_recv_header(sfrom='localhost', sid=None) - self.stream_send_header(sto='localhost') - self.stream_recv_feature(""" + self.recv_header(sfrom='localhost', sid=None) + self.send_header(sto='localhost') + self.recv_feature(""" @@ -35,15 +35,15 @@ class TestLiveStream(SleekTest): """) - self.stream_send_feature(""" + self.send_feature(""" """) - self.stream_recv_feature(""" + self.recv_feature(""" """) - self.stream_send_header(sto='localhost') - self.stream_recv_header(sfrom='localhost', sid=None) - self.stream_recv_feature(""" + self.send_header(sto='localhost') + self.recv_header(sfrom='localhost', sid=None) + self.recv_feature(""" DIGEST-MD5 @@ -56,16 +56,16 @@ class TestLiveStream(SleekTest): """) - self.stream_send_feature(""" + self.send_feature(""" AHVzZXIAdXNlcg== """) - self.stream_recv_feature(""" + self.recv_feature(""" """) - self.stream_send_header(sto='localhost') - self.stream_recv_header(sfrom='localhost', sid=None) - self.stream_recv_feature(""" + self.send_header(sto='localhost') + self.recv_header(sfrom='localhost', sid=None) + self.recv_feature(""" @@ -77,16 +77,16 @@ class TestLiveStream(SleekTest): """) - # Should really use stream_send_iq, but our Iq stanza objects + # Should really use send, but our Iq stanza objects # can't handle bind element payloads yet. - self.stream_send_feature(""" + self.send_feature(""" test """) - self.stream_recv_feature(""" + self.recv_feature(""" user@localhost/test diff --git a/tests/test_jid.py b/tests/test_jid.py index 6ec6e02..9940299 100644 --- a/tests/test_jid.py +++ b/tests/test_jid.py @@ -8,7 +8,7 @@ class TestJIDClass(SleekTest): def testJIDFromFull(self): """Test using JID of the form 'user@server/resource/with/slashes'.""" - self.check_JID(JID('user@someserver/some/resource'), + self.check_jid(JID('user@someserver/some/resource'), 'user', 'someserver', 'some/resource', @@ -22,7 +22,7 @@ class TestJIDClass(SleekTest): j.user = 'user' j.domain = 'someserver' j.resource = 'some/resource' - self.check_JID(j, + self.check_jid(j, 'user', 'someserver', 'some/resource', @@ -34,15 +34,15 @@ class TestJIDClass(SleekTest): """Test changing JID using aliases for domain.""" j = JID('user@someserver/resource') j.server = 'anotherserver' - self.check_JID(j, domain='anotherserver') + self.check_jid(j, domain='anotherserver') j.host = 'yetanother' - self.check_JID(j, domain='yetanother') + self.check_jid(j, domain='yetanother') def testJIDSetFullWithUser(self): """Test setting the full JID with a user portion.""" j = JID('user@domain/resource') j.full = 'otheruser@otherdomain/otherresource' - self.check_JID(j, + self.check_jid(j, 'otheruser', 'otherdomain', 'otherresource', @@ -57,7 +57,7 @@ class TestJIDClass(SleekTest): """ j = JID('user@domain/resource') j.full = 'otherdomain/otherresource' - self.check_JID(j, + self.check_jid(j, '', 'otherdomain', 'otherresource', @@ -72,7 +72,7 @@ class TestJIDClass(SleekTest): """ j = JID('user@domain/resource') j.full = 'otherdomain' - self.check_JID(j, + self.check_jid(j, '', 'otherdomain', '', @@ -84,7 +84,7 @@ class TestJIDClass(SleekTest): """Test setting the bare JID with a user.""" j = JID('user@domain/resource') j.bare = 'otheruser@otherdomain' - self.check_JID(j, + self.check_jid(j, 'otheruser', 'otherdomain', 'resource', @@ -96,7 +96,7 @@ class TestJIDClass(SleekTest): """Test setting the bare JID without a user.""" j = JID('user@domain/resource') j.bare = 'otherdomain' - self.check_JID(j, + self.check_jid(j, '', 'otherdomain', 'resource', @@ -106,7 +106,7 @@ class TestJIDClass(SleekTest): def testJIDNoResource(self): """Test using JID of the form 'user@domain'.""" - self.check_JID(JID('user@someserver'), + self.check_jid(JID('user@someserver'), 'user', 'someserver', '', @@ -116,7 +116,7 @@ class TestJIDClass(SleekTest): def testJIDNoUser(self): """Test JID of the form 'component.domain.tld'.""" - self.check_JID(JID('component.someserver'), + self.check_jid(JID('component.someserver'), '', 'component.someserver', '', diff --git a/tests/test_stanza_element.py b/tests/test_stanza_element.py index d361d6f..f7387d3 100644 --- a/tests/test_stanza_element.py +++ b/tests/test_stanza_element.py @@ -27,7 +27,7 @@ class TestElementBase(SleekTest): namespace = "test" stanza = TestStanza() - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ @@ -117,7 +117,7 @@ class TestElementBase(SleekTest): 'baz': ''}]} stanza.setStanzaValues(values) - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ @@ -198,7 +198,7 @@ class TestElementBase(SleekTest): stanza['qux'] = 'overridden' stanza['foobar'] = 'plugin' - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ element! @@ -231,7 +231,7 @@ class TestElementBase(SleekTest): stanza['qux'] = 'c' stanza['foobar']['foobar'] = 'd' - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ a @@ -243,7 +243,7 @@ class TestElementBase(SleekTest): del stanza['qux'] del stanza['foobar'] - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ """) @@ -257,7 +257,7 @@ class TestElementBase(SleekTest): stanza = TestStanza() - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ """) @@ -267,7 +267,7 @@ class TestElementBase(SleekTest): stanza._set_attr('bar', 'a') stanza._set_attr('baz', 'b') - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ """) @@ -277,7 +277,7 @@ class TestElementBase(SleekTest): stanza._set_attr('bar', None) stanza._del_attr('baz') - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ """) @@ -307,7 +307,7 @@ class TestElementBase(SleekTest): "Default _get_sub_text value incorrect.") stanza['bar'] = 'found' - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ found @@ -340,7 +340,7 @@ class TestElementBase(SleekTest): stanza = TestStanza() stanza['bar'] = 'a' stanza['baz'] = 'b' - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ a @@ -349,7 +349,7 @@ class TestElementBase(SleekTest): """) stanza._set_sub_text('wrapper/bar', text='', keep=True) - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ @@ -360,7 +360,7 @@ class TestElementBase(SleekTest): stanza['bar'] = 'a' stanza._set_sub_text('wrapper/bar', text='') - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ b @@ -398,7 +398,7 @@ class TestElementBase(SleekTest): stanza['bar'] = 'a' stanza['baz'] = 'b' - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ @@ -416,7 +416,7 @@ class TestElementBase(SleekTest): del stanza['bar'] del stanza['baz'] - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ @@ -432,7 +432,7 @@ class TestElementBase(SleekTest): stanza._del_sub('path/to/only/bar', all=True) - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ @@ -603,7 +603,7 @@ class TestElementBase(SleekTest): "Incorrect empty stanza size.") stanza.append(substanza1) - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ @@ -612,7 +612,7 @@ class TestElementBase(SleekTest): "Incorrect stanza size with 1 substanza.") stanza.append(substanza2) - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ @@ -623,7 +623,7 @@ class TestElementBase(SleekTest): # Test popping substanzas stanza.pop(0) - self.check_stanza(TestStanza, stanza, """ + self.check(stanza, """ diff --git a/tests/test_stanza_error.py b/tests/test_stanza_error.py index b2d9817..e1c7d5a 100644 --- a/tests/test_stanza_error.py +++ b/tests/test_stanza_error.py @@ -7,7 +7,7 @@ class TestErrorStanzas(SleekTest): """Test setting initial values in error stanza.""" msg = self.Message() msg.enable('error') - self.check_message(msg, """ + self.check(msg, """ @@ -20,7 +20,7 @@ class TestErrorStanzas(SleekTest): msg = self.Message() msg['error']['condition'] = 'item-not-found' - self.check_message(msg, """ + self.check(msg, """ @@ -32,7 +32,7 @@ class TestErrorStanzas(SleekTest): msg['error']['condition'] = 'resource-constraint' - self.check_message(msg, """ + self.check(msg, """ @@ -48,7 +48,7 @@ class TestErrorStanzas(SleekTest): del msg['error']['condition'] - self.check_message(msg, """ + self.check(msg, """ Error! @@ -64,7 +64,7 @@ class TestErrorStanzas(SleekTest): del msg['error']['text'] - self.check_message(msg, """ + self.check(msg, """ diff --git a/tests/test_stanza_gmail.py b/tests/test_stanza_gmail.py index 8ff65a3..6190c60 100644 --- a/tests/test_stanza_gmail.py +++ b/tests/test_stanza_gmail.py @@ -18,7 +18,7 @@ class TestGmail(SleekTest): iq['gmail']['newer-than-time'] = '1140638252542' iq['gmail']['newer-than-tid'] = '11134623426430234' - self.check_iq(iq, """ + self.check(iq, """ """) @@ -19,7 +19,7 @@ class TestIqStanzas(SleekTest): """Test setting Iq stanza payload.""" iq = self.Iq() iq.setPayload(ET.Element('{test}tester')) - self.check_iq(iq, """ + self.check(iq, """ @@ -29,7 +29,7 @@ class TestIqStanzas(SleekTest): def testUnhandled(self): """Test behavior for Iq.unhandled.""" self.stream_start() - self.stream_recv(""" + self.recv(""" @@ -40,7 +40,7 @@ class TestIqStanzas(SleekTest): iq['error']['condition'] = 'feature-not-implemented' iq['error']['text'] = 'No handlers registered for this request.' - self.stream_send_iq(iq, """ + self.send(iq, """ @@ -56,14 +56,14 @@ class TestIqStanzas(SleekTest): iq = self.Iq() iq['query'] = 'query_ns' - self.check_iq(iq, """ + self.check(iq, """ """) iq['query'] = 'query_ns2' - self.check_iq(iq, """ + self.check(iq, """ @@ -72,7 +72,7 @@ class TestIqStanzas(SleekTest): self.failUnless(iq['query'] == 'query_ns2', "Query namespace doesn't match") del iq['query'] - self.check_iq(iq, """ + self.check(iq, """ """) @@ -83,7 +83,7 @@ class TestIqStanzas(SleekTest): iq['type'] = 'get' iq.reply() - self.check_iq(iq, """ + self.check(iq, """ """) diff --git a/tests/test_stanza_message.py b/tests/test_stanza_message.py index 3ffe334..f06b025 100644 --- a/tests/test_stanza_message.py +++ b/tests/test_stanza_message.py @@ -33,7 +33,7 @@ class TestMessageStanzas(SleekTest): p = ET.Element('{http://www.w3.org/1999/xhtml}p') p.text = "This is the htmlim message" msg['html']['body'] = p - self.check_message(msg, """ + self.check(msg, """ this is the plaintext message @@ -47,7 +47,7 @@ class TestMessageStanzas(SleekTest): "Test message/nick/nick stanza." msg = self.Message() msg['nick']['nick'] = 'A nickname!' - self.check_message(msg, """ + self.check(msg, """ A nickname! diff --git a/tests/test_stanza_presence.py b/tests/test_stanza_presence.py index 6a5a8b6..8d043d5 100644 --- a/tests/test_stanza_presence.py +++ b/tests/test_stanza_presence.py @@ -8,26 +8,26 @@ class TestPresenceStanzas(SleekTest): """Regression check presence['type'] = 'dnd' show value working""" p = self.Presence() p['type'] = 'dnd' - self.check_presence(p, "dnd") + self.check(p, "dnd") def testPresenceType(self): """Test manipulating presence['type']""" p = self.Presence() p['type'] = 'available' - self.check_presence(p, "") + self.check(p, "") self.failUnless(p['type'] == 'available', "Incorrect presence['type'] for type 'available': %s" % p['type']) for showtype in ['away', 'chat', 'dnd', 'xa']: p['type'] = showtype - self.check_presence(p, """ + self.check(p, """ %s """ % showtype) self.failUnless(p['type'] == showtype, "Incorrect presence['type'] for type '%s'" % showtype) p['type'] = None - self.check_presence(p, "") + self.check(p, "") def testPresenceUnsolicitedOffline(self): """ @@ -56,7 +56,7 @@ class TestPresenceStanzas(SleekTest): """Test presence/nick/nick stanza.""" p = self.Presence() p['nick']['nick'] = 'A nickname!' - self.check_presence(p, """ + self.check(p, """ A nickname! diff --git a/tests/test_stanza_roster.py b/tests/test_stanza_roster.py index a0e5798..cd3e607 100644 --- a/tests/test_stanza_roster.py +++ b/tests/test_stanza_roster.py @@ -16,7 +16,7 @@ class TestRosterStanzas(SleekTest): 'name': 'Other User', 'subscription': 'both', 'groups': []}}) - self.check_iq(iq, """ + self.check(iq, """ @@ -74,7 +74,7 @@ class TestRosterStanzas(SleekTest): """ iq = self.Iq(ET.fromstring(xml_string)) del iq['roster']['items'] - self.check_iq(iq, """ + self.check(iq, """ diff --git a/tests/test_stanza_xep_0004.py b/tests/test_stanza_xep_0004.py index 5b3e490..bdc4a87 100644 --- a/tests/test_stanza_xep_0004.py +++ b/tests/test_stanza_xep_0004.py @@ -14,7 +14,7 @@ class TestDataForms(SleekTest): msg = self.Message() msg['form']['instructions'] = "Instructions\nSecond batch" - self.check_message(msg, """ + self.check(msg, """ Instructions @@ -35,7 +35,7 @@ class TestDataForms(SleekTest): required=True, value='Some text!') - self.check_message(msg, """ + self.check(msg, """ @@ -62,7 +62,7 @@ class TestDataForms(SleekTest): 'value': 'cool'}, {'label': 'Urgh!', 'value': 'urgh'}]})] - self.check_message(msg, """ + self.check(msg, """ @@ -99,7 +99,7 @@ class TestDataForms(SleekTest): form.setValues({'foo': 'Foo!', 'bar': ['a', 'b']}) - self.check_message(msg, """ + self.check(msg, """ diff --git a/tests/test_stanza_xep_0030.py b/tests/test_stanza_xep_0030.py index c61583b..e367c8d 100644 --- a/tests/test_stanza_xep_0030.py +++ b/tests/test_stanza_xep_0030.py @@ -14,7 +14,7 @@ class TestDisco(SleekTest): iq['id'] = "0" iq['disco_info']['node'] = '' - self.check_iq(iq, """ + self.check(iq, """ @@ -26,7 +26,7 @@ class TestDisco(SleekTest): iq['id'] = "0" iq['disco_info']['node'] = 'foo' - self.check_iq(iq, """ + self.check(iq, """ @@ -38,7 +38,7 @@ class TestDisco(SleekTest): iq['id'] = "0" iq['disco_items']['node'] = '' - self.check_iq(iq, """ + self.check(iq, """ @@ -50,7 +50,7 @@ class TestDisco(SleekTest): iq['id'] = "0" iq['disco_items']['node'] = 'foo' - self.check_iq(iq, """ + self.check(iq, """ @@ -63,7 +63,7 @@ class TestDisco(SleekTest): iq['disco_info']['node'] = 'foo' iq['disco_info'].addIdentity('conference', 'text', 'Chatroom') - self.check_iq(iq, """ + self.check(iq, """ @@ -79,7 +79,7 @@ class TestDisco(SleekTest): iq['disco_info'].addFeature('foo') iq['disco_info'].addFeature('bar') - self.check_iq(iq, """ + self.check(iq, """ @@ -97,7 +97,7 @@ class TestDisco(SleekTest): iq['disco_items'].addItem('user@localhost', 'foo') iq['disco_items'].addItem('user@localhost', 'bar', 'Testing') - self.check_iq(iq, """ + self.check(iq, """ diff --git a/tests/test_stanza_xep_0033.py b/tests/test_stanza_xep_0033.py index 378946a..ec9a530 100644 --- a/tests/test_stanza_xep_0033.py +++ b/tests/test_stanza_xep_0033.py @@ -11,7 +11,7 @@ class TestAddresses(SleekTest): """Testing adding extended stanza address.""" msg = self.Message() msg['addresses'].addAddress(atype='to', jid='to@header1.org') - self.check_message(msg, """ + self.check(msg, """
@@ -23,7 +23,7 @@ class TestAddresses(SleekTest): msg['addresses'].addAddress(atype='replyto', jid='replyto@header1.org', desc='Reply address') - self.check_message(msg, """ + self.check(msg, """
@@ -53,14 +53,14 @@ class TestAddresses(SleekTest): 'jid':'cc@header2.org'}, {'type':'bcc', 'jid':'bcc@header2.org'}]) - self.check_message(msg, xmlstring) + self.check(msg, xmlstring) msg = self.Message() msg['addresses']['replyto'] = [{'jid':'replyto@header1.org', 'desc':'Reply address'}] msg['addresses']['cc'] = [{'jid':'cc@header2.org'}] msg['addresses']['bcc'] = [{'jid':'bcc@header2.org'}] - self.check_message(msg, xmlstring) + self.check(msg, xmlstring) def testAddURI(self): """Testing adding URI attribute to extended stanza address.""" @@ -69,7 +69,7 @@ class TestAddresses(SleekTest): addr = msg['addresses'].addAddress(atype='to', jid='to@header1.org', node='foo') - self.check_message(msg, """ + self.check(msg, """
@@ -78,7 +78,7 @@ class TestAddresses(SleekTest): """) addr['uri'] = 'mailto:to@header2.org' - self.check_message(msg, """ + self.check(msg, """
@@ -99,13 +99,13 @@ class TestAddresses(SleekTest): msg = self.Message() addr = msg['addresses'].addAddress(jid='to@header1.org', atype='to') - self.check_message(msg, xmlstring % '') + self.check(msg, xmlstring % '') addr['delivered'] = True - self.check_message(msg, xmlstring % 'delivered="true"') + self.check(msg, xmlstring % 'delivered="true"') addr['delivered'] = False - self.check_message(msg, xmlstring % '') + self.check(msg, xmlstring % '') suite = unittest.TestLoader().loadTestsFromTestCase(TestAddresses) diff --git a/tests/test_stanza_xep_0060.py b/tests/test_stanza_xep_0060.py index 32ee37a..5d45523 100644 --- a/tests/test_stanza_xep_0060.py +++ b/tests/test_stanza_xep_0060.py @@ -16,7 +16,7 @@ class TestPubsubStanzas(SleekTest): aff2['affiliation'] = 'publisher' iq['pubsub']['affiliations'].append(aff1) iq['pubsub']['affiliations'].append(aff2) - self.check_iq(iq, """ + self.check(iq, """ @@ -38,7 +38,7 @@ class TestPubsubStanzas(SleekTest): sub2['subscription'] = 'subscribed' iq['pubsub']['subscriptions'].append(sub1) iq['pubsub']['subscriptions'].append(sub2) - self.check_iq(iq, """ + self.check(iq, """ @@ -55,7 +55,7 @@ class TestPubsubStanzas(SleekTest): iq['pubsub']['subscription']['node'] = 'testnode alsdkjfas' iq['pubsub']['subscription']['jid'] = "fritzy@netflint.net/sleekxmpp" iq['pubsub']['subscription']['subscription'] = 'unconfigured' - self.check_iq(iq, """ + self.check(iq, """ @@ -88,7 +88,7 @@ class TestPubsubStanzas(SleekTest): item2['payload'] = payload2 iq['pubsub']['items'].append(item) iq['pubsub']['items'].append(item2) - self.check_iq(iq, """ + self.check(iq, """ @@ -115,7 +115,7 @@ class TestPubsubStanzas(SleekTest): iq['pubsub']['configure']['form'].addField('pubsub#title', ftype='text-single', value='This thing is awesome') - self.check_iq(iq, """ + self.check(iq, """ @@ -136,7 +136,7 @@ class TestPubsubStanzas(SleekTest): iq['psstate']['item']= 'myitem' pl = ET.Element('{http://andyet.net/protocol/pubsubqueue}claimed') iq['psstate']['payload'] = pl - self.check_iq(iq, """ + self.check(iq, """ @@ -152,7 +152,7 @@ class TestPubsubStanzas(SleekTest): iq['pubsub_owner']['default']['form'].addField('pubsub#title', ftype='text-single', value='This thing is awesome') - self.check_iq(iq, """ + self.check(iq, """ @@ -176,7 +176,7 @@ class TestPubsubStanzas(SleekTest): form = xep_0004.Form() form.addField('pubsub#title', ftype='text-single', value='this thing is awesome') iq['pubsub']['subscribe']['options']['options'] = form - self.check_iq(iq, """ + self.check(iq, """ @@ -214,7 +214,7 @@ class TestPubsubStanzas(SleekTest): iq['pubsub']['publish'].append(item) iq['pubsub']['publish'].append(item2) - self.check_iq(iq, """ + self.check(iq, """ @@ -238,7 +238,7 @@ class TestPubsubStanzas(SleekTest): "Testing iq/pubsub_owner/delete stanzas" iq = self.Iq() iq['pubsub_owner']['delete']['node'] = 'thingers' - self.check_iq(iq, """ + self.check(iq, """ @@ -300,7 +300,7 @@ class TestPubsubStanzas(SleekTest): 'label': 'Deliver notification only to available users'}), ]) - self.check_iq(iq, """ + self.check(iq, """ @@ -357,7 +357,7 @@ class TestPubsubStanzas(SleekTest): msg['pubsub_event']['items'].append(item) msg['pubsub_event']['items']['node'] = 'cheese' msg['type'] = 'normal' - self.check_message(msg, """ + self.check(msg, """ @@ -383,7 +383,7 @@ class TestPubsubStanzas(SleekTest): msg['pubsub_event']['items'].append(item2) msg['pubsub_event']['items']['node'] = 'cheese' msg['type'] = 'normal' - self.check_message(msg, """ + self.check(msg, """ @@ -415,7 +415,7 @@ class TestPubsubStanzas(SleekTest): msg['pubsub_event']['items'].append(item2) msg['pubsub_event']['items']['node'] = 'cheese' msg['type'] = 'normal' - self.check_message(msg, """ + self.check(msg, """ @@ -435,7 +435,7 @@ class TestPubsubStanzas(SleekTest): msg['pubsub_event']['collection']['associate']['node'] = 'cheese' msg['pubsub_event']['collection']['node'] = 'cheeseburger' msg['type'] = 'headline' - self.check_message(msg, """ + self.check(msg, """ @@ -450,7 +450,7 @@ class TestPubsubStanzas(SleekTest): msg['pubsub_event']['collection']['disassociate']['node'] = 'cheese' msg['pubsub_event']['collection']['node'] = 'cheeseburger' msg['type'] = 'headline' - self.check_message(msg, """ + self.check(msg, """ @@ -467,7 +467,7 @@ class TestPubsubStanzas(SleekTest): ftype='text-single', value='This thing is awesome') msg['type'] = 'headline' - self.check_message(msg, """ + self.check(msg, """ @@ -485,7 +485,7 @@ class TestPubsubStanzas(SleekTest): msg = self.Message() msg['pubsub_event']['purge']['node'] = 'pickles' msg['type'] = 'headline' - self.check_message(msg, """ + self.check(msg, """ @@ -501,7 +501,7 @@ class TestPubsubStanzas(SleekTest): msg['pubsub_event']['subscription']['subscription'] = 'subscribed' msg['pubsub_event']['subscription']['expiry'] = 'presence' msg['type'] = 'headline' - self.check_message(msg, """ + self.check(msg, """ diff --git a/tests/test_stanza_xep_0085.py b/tests/test_stanza_xep_0085.py index a05ab4c..5db7139 100644 --- a/tests/test_stanza_xep_0085.py +++ b/tests/test_stanza_xep_0085.py @@ -21,24 +21,24 @@ class TestChatStates(SleekTest): msg = self.Message() msg['chat_state'].active() - self.check_message(msg, xmlstring % 'active', + self.check(msg, xmlstring % 'active', use_values=False) msg['chat_state'].composing() - self.check_message(msg, xmlstring % 'composing', + self.check(msg, xmlstring % 'composing', use_values=False) msg['chat_state'].gone() - self.check_message(msg, xmlstring % 'gone', + self.check(msg, xmlstring % 'gone', use_values=False) msg['chat_state'].inactive() - self.check_message(msg, xmlstring % 'inactive', + self.check(msg, xmlstring % 'inactive', use_values=False) msg['chat_state'].paused() - self.check_message(msg, xmlstring % 'paused', + self.check(msg, xmlstring % 'paused', use_values=False) suite = unittest.TestLoader().loadTestsFromTestCase(TestChatStates) diff --git a/tests/test_stream.py b/tests/test_stream.py index 3fbf86e..f91f71f 100644 --- a/tests/test_stream.py +++ b/tests/test_stream.py @@ -19,13 +19,13 @@ class TestStreamTester(SleekTest): self.xmpp.add_event_handler('message', echo) - self.stream_recv(""" + self.recv(""" Hi! """) - self.stream_send_message(""" + self.send(""" Thanks for sending: Hi! @@ -40,13 +40,13 @@ class TestStreamTester(SleekTest): self.xmpp.add_event_handler('message', echo) - self.stream_recv(""" + self.recv(""" Hi! """) - self.stream_send_message(""" + self.send(""" Thanks for sending: Hi! @@ -55,6 +55,6 @@ class TestStreamTester(SleekTest): def testSendStreamHeader(self): """Test that we can check a sent stream header.""" self.stream_start(mode='client', skip=False) - self.stream_send_header(sto='localhost') + self.send_header(sto='localhost') suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamTester) diff --git a/tests/test_stream_exceptions.py b/tests/test_stream_exceptions.py index f788a3a..b7be648 100644 --- a/tests/test_stream_exceptions.py +++ b/tests/test_stream_exceptions.py @@ -26,13 +26,13 @@ class TestStreamExceptions(SleekTest): self.stream_start() self.xmpp.add_event_handler('message', message) - self.stream_recv(""" + self.recv(""" This is going to cause an error. """) - self.stream_send_message(""" + self.send(""" This is going to cause an error. """) - self.stream_send_message(""" + self.send(""" This is going to cause an error. """) if sys.version_info < (3, 0): - self.stream_send_message(""" + self.send(""" """) + self.recv("""""") msg = self.Message() msg['body'] = 'Success!' - self.stream_send_message(msg) + self.send(msg) def testWaiter(self): """Test using stream waiter handler.""" @@ -55,7 +55,7 @@ class TestHandlers(SleekTest): self.xmpp.add_event_handler('message', waiter_handler, threaded=True) # Send message to trigger waiter_handler - self.stream_recv(""" + self.recv(""" Testing @@ -66,10 +66,10 @@ class TestHandlers(SleekTest): iq['id'] = 'test' iq['type'] = 'set' iq['query'] = 'test' - self.stream_send_iq(iq) + self.send(iq) # Send the reply Iq - self.stream_recv(""" + self.recv(""" @@ -78,7 +78,7 @@ class TestHandlers(SleekTest): # Check that waiter_handler received the reply msg = self.Message() msg['body'] = 'Successful: test' - self.stream_send_message(msg) + self.send(msg) def testWaiterTimeout(self): """Test that waiter handler is removed after timeout.""" @@ -93,14 +93,14 @@ class TestHandlers(SleekTest): self.xmpp.add_event_handler('message', waiter_handler, threaded=True) # Start test by triggerig waiter_handler - self.stream_recv("""Start Test""") + self.recv("""Start Test""") # Check that Iq was sent to trigger start of timeout period iq = self.Iq() iq['id'] = 'test2' iq['type'] = 'set' iq['query'] = 'test2' - self.stream_send_iq(iq) + self.send(iq) # Check that the waiter is no longer registered waiter_exists = self.xmpp.removeHandler('IqWait_test2') diff --git a/tests/test_stream_presence.py b/tests/test_stream_presence.py index ca67f1d..1d5caa9 100644 --- a/tests/test_stream_presence.py +++ b/tests/test_stream_presence.py @@ -29,7 +29,7 @@ class TestStreamPresence(SleekTest): self.xmpp.add_event_handler('got_offline', got_offline) self.xmpp.add_event_handler('presence_unavailable', unavailable) - self.stream_recv(""" + self.recv(""" """) @@ -54,7 +54,7 @@ class TestStreamPresence(SleekTest): # # We use the stream to initialize the roster to make # the test independent of the roster implementation. - self.stream_recv(""" + self.recv(""" """) # Contact goes offline, should trigger got_offline. - self.stream_recv(""" + self.recv(""" """) @@ -98,7 +98,7 @@ class TestStreamPresence(SleekTest): self.xmpp.add_event_handler('presence_available', presence_available) self.xmpp.add_event_handler('got_online', got_online) - self.stream_recv(""" + self.recv(""" """) @@ -135,15 +135,15 @@ class TestStreamPresence(SleekTest): self.xmpp.auto_authorize = True self.xmpp.auto_subscribe = True - self.stream_recv(""" + self.recv(""" """) - self.stream_send_presence(""" + self.send(""" """) - self.stream_send_presence(""" + self.send(""" """) @@ -172,11 +172,11 @@ class TestStreamPresence(SleekTest): # With this setting we should reject all subscriptions. self.xmpp.auto_authorize = False - self.stream_recv(""" + self.recv(""" """) - self.stream_send_presence(""" + self.send(""" """) diff --git a/tests/test_stream_roster.py b/tests/test_stream_roster.py index 6eda7e3..165a8bc 100644 --- a/tests/test_stream_roster.py +++ b/tests/test_stream_roster.py @@ -20,12 +20,12 @@ class TestStreamRoster(SleekTest): t = threading.Thread(name='get_roster', target=self.xmpp.get_roster) t.start() - self.stream_send_iq(""" + self.send(""" """) - self.stream_recv(""" + self.recv(""" """) - self.stream_send_iq(""" + self.send(""" From 4fb77ac8787422169566d613562127acf75a427b Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Sat, 6 Nov 2010 01:28:59 -0400 Subject: [PATCH 10/20] Logging no longer uses root logger. Each module should now log into its own logger. --- sleekxmpp/basexmpp.py | 35 +++++----- sleekxmpp/clientxmpp.py | 29 ++++---- sleekxmpp/componentxmpp.py | 5 +- sleekxmpp/plugins/gmail_notify.py | 25 ++++--- sleekxmpp/plugins/jobs.py | 10 ++- sleekxmpp/plugins/old_0004.py | 80 +++++++++++---------- sleekxmpp/plugins/xep_0004.py | 27 ++++---- sleekxmpp/plugins/xep_0012.py | 7 +- sleekxmpp/plugins/xep_0030.py | 20 +++--- sleekxmpp/plugins/xep_0045.py | 82 +++++++++++----------- sleekxmpp/plugins/xep_0060.py | 40 ++++++----- sleekxmpp/plugins/xep_0078.py | 17 +++-- sleekxmpp/plugins/xep_0085.py | 23 +++--- sleekxmpp/plugins/xep_0199.py | 14 ++-- sleekxmpp/plugins/xep_0202.py | 7 +- sleekxmpp/stanza/rootstanza.py | 5 +- sleekxmpp/thirdparty/statemachine.py | 96 +++++++++++++------------- sleekxmpp/xmlstream/handler/waiter.py | 5 +- sleekxmpp/xmlstream/matcher/xmlmask.py | 8 ++- sleekxmpp/xmlstream/scheduler.py | 7 +- sleekxmpp/xmlstream/stanzabase.py | 5 +- sleekxmpp/xmlstream/xmlstream.py | 67 +++++++++--------- 22 files changed, 342 insertions(+), 272 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 1e8441a..77191e7 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -26,6 +26,9 @@ from sleekxmpp.xmlstream.matcher import * from sleekxmpp.xmlstream.handler import * +log = logging.getLogger(__name__) + + # Flag indicating if DNS SRV records are available for use. SRV_SUPPORT = True try: @@ -192,9 +195,9 @@ class BaseXMPP(XMLStream): xep = "(XEP-%s) " % self.plugin[plugin].xep desc = (xep, self.plugin[plugin].description) - logging.debug("Loaded Plugin %s%s" % desc) + log.debug("Loaded Plugin %s%s" % desc) except: - logging.exception("Unable to load plugin: %s", plugin) + log.exception("Unable to load plugin: %s", plugin) def register_plugins(self): """ @@ -228,7 +231,7 @@ class BaseXMPP(XMLStream): if key in self.plugin: return self.plugin[key] else: - logging.warning("""Plugin "%s" is not loaded.""" % key) + log.warning("""Plugin "%s" is not loaded.""" % key) return False def get(self, key, default): @@ -446,12 +449,12 @@ class BaseXMPP(XMLStream): """ Attribute accessor for bare jid """ - logging.warning("jid property deprecated. Use boundjid.bare") + log.warning("jid property deprecated. Use boundjid.bare") return self.boundjid.bare @jid.setter def jid(self, value): - logging.warning("jid property deprecated. Use boundjid.bare") + log.warning("jid property deprecated. Use boundjid.bare") self.boundjid.bare = value @property @@ -459,12 +462,12 @@ class BaseXMPP(XMLStream): """ Attribute accessor for full jid """ - logging.warning("fulljid property deprecated. Use boundjid.full") + log.warning("fulljid property deprecated. Use boundjid.full") return self.boundjid.full @fulljid.setter def fulljid(self, value): - logging.warning("fulljid property deprecated. Use boundjid.full") + log.warning("fulljid property deprecated. Use boundjid.full") self.boundjid.full = value @property @@ -472,12 +475,12 @@ class BaseXMPP(XMLStream): """ Attribute accessor for jid resource """ - logging.warning("resource property deprecated. Use boundjid.resource") + log.warning("resource property deprecated. Use boundjid.resource") return self.boundjid.resource @resource.setter def resource(self, value): - logging.warning("fulljid property deprecated. Use boundjid.full") + log.warning("fulljid property deprecated. Use boundjid.full") self.boundjid.resource = value @property @@ -485,12 +488,12 @@ class BaseXMPP(XMLStream): """ Attribute accessor for jid usernode """ - logging.warning("username property deprecated. Use boundjid.user") + log.warning("username property deprecated. Use boundjid.user") return self.boundjid.user @username.setter def username(self, value): - logging.warning("username property deprecated. Use boundjid.user") + log.warning("username property deprecated. Use boundjid.user") self.boundjid.user = value @property @@ -498,17 +501,17 @@ class BaseXMPP(XMLStream): """ Attribute accessor for jid host """ - logging.warning("server property deprecated. Use boundjid.host") + log.warning("server property deprecated. Use boundjid.host") return self.boundjid.server @server.setter def server(self, value): - logging.warning("server property deprecated. Use boundjid.host") + log.warning("server property deprecated. Use boundjid.host") self.boundjid.server = value def set_jid(self, jid): """Rip a JID apart and claim it as our own.""" - logging.debug("setting jid to %s" % jid) + log.debug("setting jid to %s" % jid) self.boundjid.full = jid def getjidresource(self, fulljid): @@ -588,7 +591,7 @@ class BaseXMPP(XMLStream): # disconnects. Determine if this was the last connection # for the JID. if show == 'unavailable': - logging.debug("%s %s got offline" % (jid, resource)) + log.debug("%s %s got offline" % (jid, resource)) del connections[resource] if not connections and not self.roster[jid]['in_roster']: @@ -604,7 +607,7 @@ class BaseXMPP(XMLStream): self.event("changed_status", presence) if got_online: self.event("got_online", presence) - logging.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, + log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, show, status)) def _handle_subscribe(self, presence): diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index dc4d0e4..a88c5cc 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -32,6 +32,9 @@ except: SRV_SUPPORT = False +log = logging.getLogger(__name__) + + class ClientXMPP(BaseXMPP): """ @@ -133,7 +136,7 @@ class ClientXMPP(BaseXMPP): def _session_timeout_check(self): if not self.session_started_event.isSet(): - logging.debug("Session start has taken more than 15 seconds") + log.debug("Session start has taken more than 15 seconds") self.disconnect(reconnect=self.auto_reconnect) def connect(self, address=tuple()): @@ -150,19 +153,19 @@ class ClientXMPP(BaseXMPP): self.session_started_event.clear() if not address or len(address) < 2: if not self.srv_support: - logging.debug("Did not supply (address, port) to connect" + \ + log.debug("Did not supply (address, port) to connect" + \ " to and no SRV support is installed" + \ " (http://www.dnspython.org)." + \ " Continuing to attempt connection, using" + \ " server hostname from JID.") else: - logging.debug("Since no address is supplied," + \ + log.debug("Since no address is supplied," + \ "attempting SRV lookup.") try: xmpp_srv = "_xmpp-client._tcp.%s" % self.server answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV) except dns.resolver.NXDOMAIN: - logging.debug("No appropriate SRV record found." + \ + log.debug("No appropriate SRV record found." + \ " Using JID server name.") else: # Pick a random server, weighted by priority. @@ -276,7 +279,7 @@ class ClientXMPP(BaseXMPP): self.send_xml(xml) return True else: - logging.warning("The module tlslite is required to log in" +\ + log.warning("The module tlslite is required to log in" +\ " to some servers, and has not been found.") return False @@ -286,7 +289,7 @@ class ClientXMPP(BaseXMPP): Restarts the stream. """ - logging.debug("Starting TLS") + log.debug("Starting TLS") if self.start_tls(): raise RestartStream() @@ -300,7 +303,7 @@ class ClientXMPP(BaseXMPP): if '{urn:ietf:params:xml:ns:xmpp-tls}starttls' in self.features: return False - logging.debug("Starting SASL Auth") + log.debug("Starting SASL Auth") sasl_ns = 'urn:ietf:params:xml:ns:xmpp-sasl' self.add_handler("" % sasl_ns, self._handle_auth_success, @@ -334,7 +337,7 @@ class ClientXMPP(BaseXMPP): sasl_ns, 'ANONYMOUS')) else: - logging.error("No appropriate login method.") + log.error("No appropriate login method.") self.disconnect() return True @@ -356,7 +359,7 @@ class ClientXMPP(BaseXMPP): Arguments: xml -- The SASL authentication failure element. """ - logging.info("Authentication failed.") + log.info("Authentication failed.") self.event("failed_auth", direct=True) self.disconnect() @@ -367,7 +370,7 @@ class ClientXMPP(BaseXMPP): Arguments: xml -- The bind feature element. """ - logging.debug("Requesting resource: %s" % self.boundjid.resource) + log.debug("Requesting resource: %s" % self.boundjid.resource) xml.clear() iq = self.Iq(stype='set') if self.boundjid.resource: @@ -381,10 +384,10 @@ class ClientXMPP(BaseXMPP): self.set_jid(response.xml.find('{%s}bind/{%s}jid' % (bind_ns, bind_ns)).text) self.bound = True - logging.info("Node set to: %s" % self.boundjid.fulljid) + log.info("Node set to: %s" % self.boundjid.fulljid) session_ns = 'urn:ietf:params:xml:ns:xmpp-session' if "{%s}session" % session_ns not in self.features or self.bindfail: - logging.debug("Established Session") + log.debug("Established Session") self.sessionstarted = True self.session_started_event.set() self.event("session_start") @@ -399,7 +402,7 @@ class ClientXMPP(BaseXMPP): if self.authenticated and self.bound: iq = self.makeIqSet(xml) response = iq.send() - logging.debug("Established Session") + log.debug("Established Session") self.sessionstarted = True self.session_started_event.set() self.event("session_start") diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index 3fcb88d..8b0b6cc 100644 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -22,6 +22,9 @@ from sleekxmpp.xmlstream.matcher import * from sleekxmpp.xmlstream.handler import * +log = logging.getLogger(__name__) + + class ComponentXMPP(BaseXMPP): """ @@ -82,7 +85,7 @@ class ComponentXMPP(BaseXMPP): Overrides XMLStream.connect. """ - logging.debug("Connecting to %s:%s" % (self.server_host, + log.debug("Connecting to %s:%s" % (self.server_host, self.server_port)) return XMLStream.connect(self, self.server_host, self.server_port) diff --git a/sleekxmpp/plugins/gmail_notify.py b/sleekxmpp/plugins/gmail_notify.py index 7e44234..7e888b9 100644 --- a/sleekxmpp/plugins/gmail_notify.py +++ b/sleekxmpp/plugins/gmail_notify.py @@ -14,6 +14,9 @@ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq +log = logging.getLogger(__name__) + + class GmailQuery(ElementBase): namespace = 'google:mail:notify' name = 'query' @@ -34,12 +37,12 @@ class MailBox(ElementBase): namespace = 'google:mail:notify' name = 'mailbox' plugin_attrib = 'mailbox' - interfaces = set(('result-time', 'total-matched', 'total-estimate', + interfaces = set(('result-time', 'total-matched', 'total-estimate', 'url', 'threads', 'matched', 'estimate')) def getThreads(self): threads = [] - for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, + for threadXML in self.xml.findall('{%s}%s' % (MailThread.namespace, MailThread.name)): threads.append(MailThread(xml=threadXML, parent=None)) return threads @@ -55,10 +58,10 @@ class MailThread(ElementBase): namespace = 'google:mail:notify' name = 'mail-thread-info' plugin_attrib = 'thread' - interfaces = set(('tid', 'participation', 'messages', 'date', + interfaces = set(('tid', 'participation', 'messages', 'date', 'senders', 'url', 'labels', 'subject', 'snippet')) sub_interfaces = set(('labels', 'subject', 'snippet')) - + def getSenders(self): senders = [] sendersXML = self.xml.find('{%s}senders' % self.namespace) @@ -91,13 +94,13 @@ class gmail_notify(base.base_plugin): """ Google Talk: Gmail Notifications """ - + def plugin_init(self): self.description = 'Google Talk: Gmail Notifications' self.xmpp.registerHandler( Callback('Gmail Result', - MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, + MatchXPath('{%s}iq/{%s}%s' % (self.xmpp.default_ns, MailBox.namespace, MailBox.name)), self.handle_gmail)) @@ -108,7 +111,7 @@ class gmail_notify(base.base_plugin): NewMail.namespace, NewMail.name)), self.handle_new_mail)) - + registerStanzaPlugin(Iq, GmailQuery) registerStanzaPlugin(Iq, MailBox) registerStanzaPlugin(Iq, NewMail) @@ -118,12 +121,12 @@ class gmail_notify(base.base_plugin): def handle_gmail(self, iq): mailbox = iq['mailbox'] approx = ' approximately' if mailbox['estimated'] else '' - logging.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched'])) + log.info('Gmail: Received%s %s emails' % (approx, mailbox['total-matched'])) self.last_result_time = mailbox['result-time'] self.xmpp.event('gmail_messages', iq) def handle_new_mail(self, iq): - logging.info("Gmail: New emails received!") + log.info("Gmail: New emails received!") self.xmpp.event('gmail_notify') self.checkEmail() @@ -135,9 +138,9 @@ class gmail_notify(base.base_plugin): def search(self, query=None, newer=None): if query is None: - logging.info("Gmail: Checking for new emails") + log.info("Gmail: Checking for new emails") else: - logging.info('Gmail: Searching for emails matching: "%s"' % query) + log.info('Gmail: Searching for emails matching: "%s"' % query) iq = self.xmpp.Iq() iq['type'] = 'get' iq['to'] = self.xmpp.jid diff --git a/sleekxmpp/plugins/jobs.py b/sleekxmpp/plugins/jobs.py index c52e524..0b93d62 100644 --- a/sleekxmpp/plugins/jobs.py +++ b/sleekxmpp/plugins/jobs.py @@ -3,15 +3,19 @@ import logging from xml.etree import cElementTree as ET import types + +log = logging.getLogger(__name__) + + class jobs(base.base_plugin): def plugin_init(self): self.xep = 'pubsubjob' self.description = "Job distribution over Pubsub" - + def post_init(self): pass #TODO add event - + def createJobNode(self, host, jid, node, config=None): pass @@ -40,7 +44,7 @@ class jobs(base.base_plugin): iq['psstate']['payload'] = state result = iq.send() if result is None or type(result) == types.BooleanType or result['type'] != 'result': - logging.error("Unable to change %s:%s to %s" % (node, jobid, state)) + log.error("Unable to change %s:%s to %s" % (node, jobid, state)) return False return True diff --git a/sleekxmpp/plugins/old_0004.py b/sleekxmpp/plugins/old_0004.py index 651408a..ade3d68 100644 --- a/sleekxmpp/plugins/old_0004.py +++ b/sleekxmpp/plugins/old_0004.py @@ -2,42 +2,46 @@ SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - + See the file LICENSE for copying permission. """ from . import base -import logging +import log from xml.etree import cElementTree as ET import copy import logging #TODO support item groups and results + +log = logging.getLogger(__name__) + + class old_0004(base.base_plugin): - + def plugin_init(self): self.xep = '0004' self.description = '*Deprecated Data Forms' self.xmpp.add_handler("", self.handler_message_xform, name='Old Message Form') - + def post_init(self): base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') - logging.warning("This implementation of XEP-0004 is deprecated.") - + log.warning("This implementation of XEP-0004 is deprecated.") + def handler_message_xform(self, xml): object = self.handle_form(xml) self.xmpp.event("message_form", object) - + def handler_presence_xform(self, xml): object = self.handle_form(xml) self.xmpp.event("presence_form", object) - + def handle_form(self, xml): xmlform = xml.find('{jabber:x:data}x') object = self.buildForm(xmlform) self.xmpp.event("message_xform", object) return object - + def buildForm(self, xml): form = Form(ftype=xml.attrib['type']) form.fromXML(xml) @@ -51,12 +55,12 @@ class FieldContainer(object): self.fields = [] self.field = {} self.stanza = stanza - + def addField(self, var, ftype='text-single', label='', desc='', required=False, value=None): self.field[var] = FormField(var, ftype, label, desc, required, value) self.fields.append(self.field[var]) return self.field[var] - + def buildField(self, xml): self.field[xml.get('var', '__unnamed__')] = FormField(xml.get('var', '__unnamed__'), xml.get('type', 'text-single')) self.fields.append(self.field[xml.get('var', '__unnamed__')]) @@ -66,13 +70,13 @@ class FieldContainer(object): self.stanza = xml.tag for field in xml.findall('{jabber:x:data}field'): self.buildField(field) - + def getXML(self, ftype): container = ET.Element(self.stanza) for field in self.fields: container.append(field.getXML(ftype)) return container - + class Form(FieldContainer): types = ('form', 'submit', 'cancel', 'result') def __init__(self, xmpp=None, ftype='form', title='', instructions=''): @@ -85,7 +89,7 @@ class Form(FieldContainer): self.instructions = instructions self.reported = [] self.items = [] - + def merge(self, form2): form1 = Form(ftype=self.type) form1.fromXML(self.getXML(self.type)) @@ -98,18 +102,18 @@ class Form(FieldContainer): if (option, label) not in form1.field[field.var].options: form1.fields[field.var].addOption(option, label) return form1 - + def copy(self): newform = Form(ftype=self.type) newform.fromXML(self.getXML(self.type)) return newform - + def update(self, form): values = form.getValues() for var in values: if var in self.fields: self.fields[var].setValue(self.fields[var]) - + def getValues(self): result = {} for field in self.fields: @@ -118,7 +122,7 @@ class Form(FieldContainer): value = value[0] result[field.var] = value return result - + def setValues(self, values={}): for field in values: if field in self.field: @@ -127,10 +131,10 @@ class Form(FieldContainer): self.field[field].setValue(value) else: self.field[field].setValue(values[field]) - + def fromXML(self, xml): self.buildForm(xml) - + def addItem(self): newitem = FieldContainer('item') self.items.append(newitem) @@ -148,21 +152,21 @@ class Form(FieldContainer): def buildReported(self, xml): reported = self.addReported() reported.buildContainer(xml) - + def setTitle(self, title): self.title = title - + def setInstructions(self, instructions): self.instructions = instructions - + def setType(self, ftype): self.type = ftype - + def getXMLMessage(self, to): msg = self.xmpp.makeMessage(to) msg.append(self.getXML()) return msg - + def buildForm(self, xml): self.type = xml.get('type', 'form') if xml.find('{jabber:x:data}title') is not None: @@ -175,7 +179,7 @@ class Form(FieldContainer): self.buildReported(reported) for item in xml.findall('{jabber:x:data}item'): self.buildItem(item) - + #def getXML(self, tostring = False): def getXML(self, ftype=None): if ftype: @@ -199,7 +203,7 @@ class Form(FieldContainer): #if tostring: # form = self.xmpp.tostring(form) return form - + def getXHTML(self): form = ET.Element('{http://www.w3.org/1999/xhtml}form') if self.title: @@ -217,8 +221,8 @@ class Form(FieldContainer): for field in self.items: form.append(field.getXHTML()) return form - - + + def makeSubmit(self): self.setType('submit') @@ -246,13 +250,13 @@ class FormField(object): self.islinebreak = False if value: self.setValue(value) - + def addOption(self, value, label): if self.islist: self.options.append((value, label)) else: raise ValueError("Cannot add options to non-list type field.") - + def setTrue(self): if self.type == 'boolean': self.value = [True] @@ -263,10 +267,10 @@ class FormField(object): def require(self): self.required = True - + def setDescription(self, desc): self.desc = desc - + def setValue(self, value): if self.type == 'boolean': if value in ('1', 1, True, 'true', 'True', 'yes'): @@ -291,10 +295,10 @@ class FormField(object): pass else: self.value = '' - + def setAnswer(self, value): self.setValue(value) - + def buildField(self, xml): self.type = xml.get('type', 'text-single') self.label = xml.get('label', '') @@ -306,7 +310,7 @@ class FormField(object): self.require() if xml.find('{jabber:x:data}desc') is not None: self.setDescription(xml.find('{jabber:x:data}desc').text) - + def getXML(self, ftype): field = ET.Element('{jabber:x:data}field') if ftype != 'result': @@ -342,7 +346,7 @@ class FormField(object): valuexml.text = value field.append(valuexml) return field - + def getXHTML(self): field = ET.Element('div', {'class': 'xmpp-xforms-%s' % self.type}) if self.label: @@ -414,4 +418,4 @@ class FormField(object): pass label.append(formf) return field - + diff --git a/sleekxmpp/plugins/xep_0004.py b/sleekxmpp/plugins/xep_0004.py index e8dba74..b8b7ebf 100644 --- a/sleekxmpp/plugins/xep_0004.py +++ b/sleekxmpp/plugins/xep_0004.py @@ -16,6 +16,9 @@ from .. stanza.message import Message import types +log = logging.getLogger(__name__) + + class Form(ElementBase): namespace = 'jabber:x:data' name = 'x' @@ -33,7 +36,7 @@ class Form(ElementBase): if title is not None: self['title'] = title self.field = FieldAccessor(self) - + def setup(self, xml=None): if ElementBase.setup(self, xml): #if we had to generate xml self['type'] = 'form' @@ -55,11 +58,11 @@ class Form(ElementBase): return field def getXML(self, type='submit'): - logging.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") + log.warning("Form.getXML() is deprecated API compatibility with plugins/old_0004.py") return self.xml - + def fromXML(self, xml): - logging.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py") + log.warning("Form.fromXML() is deprecated API compatibility with plugins/old_0004.py") n = Form(xml=xml) return n @@ -113,10 +116,10 @@ class Form(ElementBase): reportedXML = self.xml.find('{%s}reported' % self.namespace) if reportedXML is not None: self.xml.remove(reportedXML) - + def getFields(self, use_dict=False): fields = {} if use_dict else [] - fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) + fieldsXML = self.xml.findall('{%s}field' % FormField.namespace) for fieldXML in fieldsXML: field = FormField(xml=fieldXML) if use_dict: @@ -144,7 +147,7 @@ class Form(ElementBase): def getReported(self): fields = {} - fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, + fieldsXML = self.xml.findall('{%s}reported/{%s}field' % (self.namespace, FormField.namespace)) for fieldXML in fieldsXML: field = FormField(xml=fieldXML) @@ -197,7 +200,7 @@ class Form(ElementBase): fields = self.getFields(use_dict=True) for field in values: fields[field]['value'] = values[field] - + def merge(self, other): new = copy.copy(self) if type(other) == types.DictType: @@ -212,7 +215,7 @@ class Form(ElementBase): class FieldAccessor(object): def __init__(self, form): self.form = form - + def __getitem__(self, key): return self.form.getFields(use_dict=True)[key] @@ -366,21 +369,21 @@ class xep_0004(base.base_plugin): self.xmpp.registerHandler( Callback('Data Form', - MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns, + MatchXPath('{%s}message/{%s}x' % (self.xmpp.default_ns, Form.namespace)), self.handle_form)) registerStanzaPlugin(FormField, FieldOption) registerStanzaPlugin(Form, FormField) registerStanzaPlugin(Message, Form) - + def makeForm(self, ftype='form', title='', instructions=''): f = Form() f['type'] = ftype f['title'] = title f['instructions'] = instructions return f - + def post_init(self): base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('jabber:x:data') diff --git a/sleekxmpp/plugins/xep_0012.py b/sleekxmpp/plugins/xep_0012.py index 45ca8a0..d636d4d 100644 --- a/sleekxmpp/plugins/xep_0012.py +++ b/sleekxmpp/plugins/xep_0012.py @@ -16,6 +16,9 @@ from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin +log = logging.getLogger(__name__) + + class LastActivity(ElementBase): name = 'query' namespace = 'jabber:iq:last' @@ -68,10 +71,10 @@ class xep_0012(base.base_plugin): def handle_last_activity_query(self, iq): if iq['type'] == 'get': - logging.debug("Last activity requested by %s" % iq['from']) + log.debug("Last activity requested by %s" % iq['from']) self.xmpp.event('last_activity_request', iq) elif iq['type'] == 'result': - logging.debug("Last activity result from %s" % iq['from']) + log.debug("Last activity result from %s" % iq['from']) self.xmpp.event('last_activity', iq) def handle_last_activity(self, iq): diff --git a/sleekxmpp/plugins/xep_0030.py b/sleekxmpp/plugins/xep_0030.py index 21f4538..a3fac34 100644 --- a/sleekxmpp/plugins/xep_0030.py +++ b/sleekxmpp/plugins/xep_0030.py @@ -13,6 +13,10 @@ from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET, JID from .. stanza.iq import Iq + +log = logging.getLogger(__name__) + + class DiscoInfo(ElementBase): namespace = 'http://jabber.org/protocol/disco#info' name = 'query' @@ -222,18 +226,18 @@ class xep_0030(base.base_plugin): def handle_item_query(self, iq): if iq['type'] == 'get': - logging.debug("Items requested by %s" % iq['from']) + log.debug("Items requested by %s" % iq['from']) self.xmpp.event('disco_items_request', iq) elif iq['type'] == 'result': - logging.debug("Items result from %s" % iq['from']) + log.debug("Items result from %s" % iq['from']) self.xmpp.event('disco_items', iq) def handle_info_query(self, iq): if iq['type'] == 'get': - logging.debug("Info requested by %s" % iq['from']) + log.debug("Info requested by %s" % iq['from']) self.xmpp.event('disco_info_request', iq) elif iq['type'] == 'result': - logging.debug("Info result from %s" % iq['from']) + log.debug("Info result from %s" % iq['from']) self.xmpp.event('disco_info', iq) def handle_disco_info(self, iq, forwarded=False): @@ -248,13 +252,13 @@ class xep_0030(base.base_plugin): if not node_name: node_name = 'main' - logging.debug("Using default handler for disco#info on node '%s'." % node_name) + log.debug("Using default handler for disco#info on node '%s'." % node_name) if node_name in self.nodes: node = self.nodes[node_name] iq.reply().setPayload(node.info.xml).send() else: - logging.debug("Node %s requested, but does not exist." % node_name) + log.debug("Node %s requested, but does not exist." % node_name) iq.reply().error().setPayload(iq['disco_info'].xml) iq['error']['code'] = '404' iq['error']['type'] = 'cancel' @@ -276,13 +280,13 @@ class xep_0030(base.base_plugin): if not node_name: node_name = 'main' - logging.debug("Using default handler for disco#items on node '%s'." % node_name) + log.debug("Using default handler for disco#items on node '%s'." % node_name) if node_name in self.nodes: node = self.nodes[node_name] iq.reply().setPayload(node.items.xml).send() else: - logging.debug("Node %s requested, but does not exist." % node_name) + log.debug("Node %s requested, but does not exist." % node_name) iq.reply().error().setPayload(iq['disco_items'].xml) iq['error']['code'] = '404' iq['error']['type'] = 'cancel' diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index bf472a4..de98235 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -2,7 +2,7 @@ SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - + See the file LICENSE for copying permission. """ from __future__ import with_statement @@ -15,6 +15,10 @@ from .. xmlstream.handler.callback import Callback from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream.matcher.xmlmask import MatchXMLMask + +log = logging.getLogger(__name__) + + class MUCPresence(ElementBase): name = 'x' namespace = 'http://jabber.org/protocol/muc#user' @@ -34,79 +38,79 @@ class MUCPresence(ElementBase): #TODO if no affilation, set it to the default and return default item = self.getXMLItem() return item.get('affiliation', '') - + def setAffiliation(self, value): item = self.getXMLItem() #TODO check for valid affiliation item.attrib['affiliation'] = value return self - + def delAffiliation(self): item = self.getXMLItem() #TODO set default affiliation if 'affiliation' in item.attrib: del item.attrib['affiliation'] return self - + def getJid(self): item = self.getXMLItem() return JID(item.get('jid', '')) - + def setJid(self, value): item = self.getXMLItem() if not isinstance(value, str): value = str(value) item.attrib['jid'] = value return self - + def delJid(self): item = self.getXMLItem() if 'jid' in item.attrib: del item.attrib['jid'] return self - + def getRole(self): item = self.getXMLItem() #TODO get default role, set default role if none return item.get('role', '') - + def setRole(self, value): item = self.getXMLItem() #TODO check for valid role item.attrib['role'] = value return self - + def delRole(self): item = self.getXMLItem() #TODO set default role if 'role' in item.attrib: del item.attrib['role'] return self - + def getNick(self): return self.parent()['from'].resource - + def getRoom(self): return self.parent()['from'].bare - + def setNick(self, value): - logging.warning("Cannot set nick through mucpresence plugin.") + log.warning("Cannot set nick through mucpresence plugin.") return self - + def setRoom(self, value): - logging.warning("Cannot set room through mucpresence plugin.") + log.warning("Cannot set room through mucpresence plugin.") return self - + def delNick(self): - logging.warning("Cannot delete nick through mucpresence plugin.") + log.warning("Cannot delete nick through mucpresence plugin.") return self - + def delRoom(self): - logging.warning("Cannot delete room through mucpresence plugin.") + log.warning("Cannot delete room through mucpresence plugin.") return self class xep_0045(base.base_plugin): """ Impliments XEP-0045 Multi User Chat """ - + def plugin_init(self): self.rooms = {} self.ourNicks = {} @@ -116,7 +120,7 @@ class xep_0045(base.base_plugin): registerStanzaPlugin(Presence, MUCPresence) self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_presence)) self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_message)) - + def handle_groupchat_presence(self, pr): """ Handle a presence in a muc. """ @@ -135,27 +139,27 @@ class xep_0045(base.base_plugin): if entry['nick'] not in self.rooms[entry['room']]: got_online = True self.rooms[entry['room']][entry['nick']] = entry - logging.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry)) + log.debug("MUC presence from %s/%s : %s" % (entry['room'],entry['nick'], entry)) self.xmpp.event("groupchat_presence", pr) self.xmpp.event("muc::%s::presence" % entry['room'], pr) if got_offline: self.xmpp.event("muc::%s::got_offline" % entry['room'], pr) if got_online: self.xmpp.event("muc::%s::got_online" % entry['room'], pr) - + def handle_groupchat_message(self, msg): """ Handle a message event in a muc. """ self.xmpp.event('groupchat_message', msg) self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) - + def jidInRoom(self, room, jid): for nick in self.rooms[room]: entry = self.rooms[room][nick] if entry is not None and entry['jid'].full == jid: return True return False - + def getNick(self, room, jid): for nick in self.rooms[room]: entry = self.rooms[room][nick] @@ -176,12 +180,12 @@ class xep_0045(base.base_plugin): if xform is None: return False form = self.xmpp.plugin['old_0004'].buildForm(xform) return form - + def configureRoom(self, room, form=None, ifrom=None): if form is None: form = self.getRoomForm(room, ifrom=ifrom) #form = self.xmpp.plugin['old_0004'].makeForm(ftype='submit') - #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') + #form.addField('FORM_TYPE', value='http://jabber.org/protocol/muc#roomconfig') iq = self.xmpp.makeIqSet() iq['to'] = room if ifrom is not None: @@ -194,7 +198,7 @@ class xep_0045(base.base_plugin): if result['type'] == 'error': return False return True - + def joinMUC(self, room, nick, maxhistory="0", password='', wait=False, pstatus=None, pshow=None): """ Join the specified room, requesting 'maxhistory' lines of history. """ @@ -220,7 +224,7 @@ class xep_0045(base.base_plugin): self.xmpp.send(stanza, expect) self.rooms[room] = {} self.ourNicks[room] = nick - + def destroy(self, room, reason='', altroom = '', ifrom=None): iq = self.xmpp.makeIqSet() if ifrom is not None: @@ -246,9 +250,9 @@ class xep_0045(base.base_plugin): raise TypeError query = ET.Element('{http://jabber.org/protocol/muc#admin}query') if nick is not None: - item = ET.Element('item', {'affiliation':affiliation, 'nick':nick}) + item = ET.Element('item', {'affiliation':affiliation, 'nick':nick}) else: - item = ET.Element('item', {'affiliation':affiliation, 'jid':jid}) + item = ET.Element('item', {'affiliation':affiliation, 'jid':jid}) query.append(item) iq = self.xmpp.makeIqSet(query) iq['to'] = room @@ -256,7 +260,7 @@ class xep_0045(base.base_plugin): if result is False or result['type'] != 'result': raise ValueError return True - + def invite(self, room, jid, reason=''): """ Invite a jid to a room.""" msg = self.xmpp.makeMessage(room) @@ -279,7 +283,7 @@ class xep_0045(base.base_plugin): else: self.xmpp.sendPresence(pshow='unavailable', pto="%s/%s" % (room, nick)) del self.rooms[room] - + def getRoomConfig(self, room): iq = self.xmpp.makeIqGet('http://jabber.org/protocol/muc#owner') iq['to'] = room @@ -291,14 +295,14 @@ class xep_0045(base.base_plugin): if form is None: raise ValueError return self.xmpp.plugin['xep_0004'].buildForm(form) - + def cancelConfig(self, room): query = ET.Element('{http://jabber.org/protocol/muc#owner}query') x = ET.Element('{jabber:x:data}x', type='cancel') query.append(x) iq = self.xmpp.makeIqSet(query) iq.send() - + def setRoomConfig(self, room, config): query = ET.Element('{http://jabber.org/protocol/muc#owner}query') x = config.getXML('submit') @@ -307,15 +311,15 @@ class xep_0045(base.base_plugin): iq['to'] = room iq['from'] = self.xmpp.jid iq.send() - + def getJoinedRooms(self): return self.rooms.keys() - + def getOurJidInRoom(self, roomJid): """ Return the jid we're using in a room. """ return "%s/%s" % (roomJid, self.ourNicks[roomJid]) - + def getJidProperty(self, room, nick, jidProperty): """ Get the property of a nick in a room, such as its 'jid' or 'affiliation' If not found, return None. @@ -324,7 +328,7 @@ class xep_0045(base.base_plugin): return self.rooms[room][nick][jidProperty] else: return None - + def getRoster(self, room): """ Get the list of nicks in a room. """ diff --git a/sleekxmpp/plugins/xep_0060.py b/sleekxmpp/plugins/xep_0060.py index 0b056f0..a7c6d02 100644 --- a/sleekxmpp/plugins/xep_0060.py +++ b/sleekxmpp/plugins/xep_0060.py @@ -6,6 +6,10 @@ from .. xmlstream.stanzabase import registerStanzaPlugin, ElementBase, ET from . import stanza_pubsub from . xep_0004 import Form + +log = logging.getLogger(__name__) + + class xep_0060(base.base_plugin): """ XEP-0060 Publish Subscribe @@ -14,7 +18,7 @@ class xep_0060(base.base_plugin): def plugin_init(self): self.xep = '0060' self.description = 'Publish-Subscribe' - + def create_node(self, jid, node, config=None, collection=False, ntype=None): pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') create = ET.Element('create') @@ -52,7 +56,7 @@ class xep_0060(base.base_plugin): result = iq.send() if result is False or result is None or result['type'] == 'error': return False return True - + def subscribe(self, jid, node, bare=True, subscribee=None): pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') subscribe = ET.Element('subscribe') @@ -72,7 +76,7 @@ class xep_0060(base.base_plugin): result = iq.send() if result is False or result is None or result['type'] == 'error': return False return True - + def unsubscribe(self, jid, node, bare=True, subscribee=None): pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') unsubscribe = ET.Element('unsubscribe') @@ -92,7 +96,7 @@ class xep_0060(base.base_plugin): result = iq.send() if result is False or result is None or result['type'] == 'error': return False return True - + def getNodeConfig(self, jid, node=None): # if no node, then grab default pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') if node is not None: @@ -110,17 +114,17 @@ class xep_0060(base.base_plugin): #self.xmpp.add_handler("" % id, self.handlerCreateNodeResponse) result = iq.send() if result is None or result == False or result['type'] == 'error': - logging.warning("got error instead of config") + log.warning("got error instead of config") return False if node is not None: form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}configure/{jabber:x:data}x') else: form = result.find('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}default/{jabber:x:data}x') if not form or form is None: - logging.error("No form found.") + log.error("No form found.") return False return Form(xml=form) - + def getNodeSubscriptions(self, jid, node): pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') subscriptions = ET.Element('subscriptions') @@ -133,7 +137,7 @@ class xep_0060(base.base_plugin): id = iq['id'] result = iq.send() if result is None or result == False or result['type'] == 'error': - logging.warning("got error instead of config") + log.warning("got error instead of config") return False else: results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}subscriptions/{http://jabber.org/protocol/pubsub#owner}subscription') @@ -156,7 +160,7 @@ class xep_0060(base.base_plugin): id = iq['id'] result = iq.send() if result is None or result == False or result['type'] == 'error': - logging.warning("got error instead of config") + log.warning("got error instead of config") return False else: results = result.findall('{http://jabber.org/protocol/pubsub#owner}pubsub/{http://jabber.org/protocol/pubsub#owner}affiliations/{http://jabber.org/protocol/pubsub#owner}affiliation') @@ -181,8 +185,8 @@ class xep_0060(base.base_plugin): return True else: return False - - + + def setNodeConfig(self, jid, node, config): pubsub = ET.Element('{http://jabber.org/protocol/pubsub#owner}pubsub') configure = ET.Element('configure') @@ -195,10 +199,10 @@ class xep_0060(base.base_plugin): iq.attrib['from'] = self.xmpp.fulljid id = iq['id'] result = iq.send() - if result is None or result['type'] == 'error': + if result is None or result['type'] == 'error': return False return True - + def setItem(self, jid, node, items=[]): pubsub = ET.Element('{http://jabber.org/protocol/pubsub}pubsub') publish = ET.Element('publish') @@ -218,7 +222,7 @@ class xep_0060(base.base_plugin): result = iq.send() if result is None or result is False or result['type'] == 'error': return False return True - + def addItem(self, jid, node, items=[]): return self.setItem(jid, node, items) @@ -237,7 +241,7 @@ class xep_0060(base.base_plugin): result = iq.send() if result is None or result is False or result['type'] == 'error': return False return True - + def getNodes(self, jid): response = self.xmpp.plugin['xep_0030'].getItems(jid) items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') @@ -246,7 +250,7 @@ class xep_0060(base.base_plugin): for item in items: nodes[item.get('node')] = item.get('name') return nodes - + def getItems(self, jid, node): response = self.xmpp.plugin['xep_0030'].getItems(jid, node) items = response.findall('{http://jabber.org/protocol/disco#items}query/{http://jabber.org/protocol/disco#items}item') @@ -264,7 +268,7 @@ class xep_0060(base.base_plugin): try: config.field['pubsub#collection'].setValue(parent) except KeyError: - logging.warning("pubsub#collection doesn't exist in config, trying to add it") + log.warning("pubsub#collection doesn't exist in config, trying to add it") config.addField('pubsub#collection', value=parent) if not self.setNodeConfig(jid, child, config): return False @@ -298,7 +302,7 @@ class xep_0060(base.base_plugin): try: config.field['pubsub#collection'].setValue(parent) except KeyError: - logging.warning("pubsub#collection doesn't exist in config, trying to add it") + log.warning("pubsub#collection doesn't exist in config, trying to add it") config.addField('pubsub#collection', value=parent) if not self.setNodeConfig(jid, child, config): return False diff --git a/sleekxmpp/plugins/xep_0078.py b/sleekxmpp/plugins/xep_0078.py index 4b3ab82..d2c81b1 100644 --- a/sleekxmpp/plugins/xep_0078.py +++ b/sleekxmpp/plugins/xep_0078.py @@ -2,7 +2,7 @@ SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - + See the file LICENSE for copying permission. """ from __future__ import with_statement @@ -12,6 +12,9 @@ import hashlib from . import base +log = logging.getLogger(__name__) + + class xep_0078(base.base_plugin): """ XEP-0078 NON-SASL Authentication @@ -23,14 +26,14 @@ class xep_0078(base.base_plugin): #disabling until I fix conflict with PLAIN #self.xmpp.registerFeature("", self.auth) self.streamid = '' - + def check_stream(self, xml): self.streamid = xml.attrib['id'] if xml.get('version', '0') != '1.0': self.auth() - + def auth(self, xml=None): - logging.debug("Starting jabber:iq:auth Authentication") + log.debug("Starting jabber:iq:auth Authentication") auth_request = self.xmpp.makeIqGet() auth_request_query = ET.Element('{jabber:iq:auth}query') auth_request.attrib['to'] = self.xmpp.server @@ -47,12 +50,12 @@ class xep_0078(base.base_plugin): query.append(username) query.append(resource) if rquery.find('{jabber:iq:auth}digest') is None: - logging.warning("Authenticating via jabber:iq:auth Plain.") + log.warning("Authenticating via jabber:iq:auth Plain.") password = ET.Element('password') password.text = self.xmpp.password query.append(password) else: - logging.debug("Authenticating via jabber:iq:auth Digest") + log.debug("Authenticating via jabber:iq:auth Digest") digest = ET.Element('digest') digest.text = hashlib.sha1(b"%s%s" % (self.streamid, self.xmpp.password)).hexdigest() query.append(digest) @@ -64,6 +67,6 @@ class xep_0078(base.base_plugin): self.xmpp.sessionstarted = True self.xmpp.event("session_start") else: - logging.info("Authentication failed") + log.info("Authentication failed") self.xmpp.disconnect() self.xmpp.event("failed_auth") diff --git a/sleekxmpp/plugins/xep_0085.py b/sleekxmpp/plugins/xep_0085.py index b7b5d6d..3627e71 100644 --- a/sleekxmpp/plugins/xep_0085.py +++ b/sleekxmpp/plugins/xep_0085.py @@ -14,15 +14,18 @@ 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') @@ -67,11 +70,11 @@ 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'), @@ -79,10 +82,10 @@ class xep_0085(base.base_plugin): ('Paused Chat State', 'paused')] for handler in handlers: self.xmpp.registerHandler( - Callback(handler[0], - MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns, + Callback(handler[0], + MatchXPath("{%s}message/{%s}%s" % (self.xmpp.default_ns, ChatState.namespace, - handler[1])), + handler[1])), self._handleChatState)) registerStanzaPlugin(Message, Active) @@ -90,12 +93,12 @@ class xep_0085(base.base_plugin): 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 - logging.debug("Chat State: %s, %s" % (state, msg['from'].jid)) + log.debug("Chat State: %s, %s" % (state, msg['from'].jid)) self.xmpp.event('chatstate_%s' % state, msg) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index 1be326c..2005594 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -2,7 +2,7 @@ SleekXMPP: The Sleek XMPP Library Copyright (C) 2010 Nathanael C. Fritz This file is part of SleekXMPP. - + See the file LICENSE for copying permission. """ from xml.etree import cElementTree as ET @@ -10,6 +10,10 @@ from . import base import time import logging + +log = logging.getLogger(__name__) + + class xep_0199(base.base_plugin): """XEP-0199 XMPP Ping""" @@ -20,19 +24,19 @@ class xep_0199(base.base_plugin): self.running = False if self.config.get('keepalive', True): self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) - + def post_init(self): base.base_plugin.post_init(self) self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:ping') - + def handler_pingserver(self, xml): if not self.running: time.sleep(self.config.get('frequency', 300)) while self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is not False: time.sleep(self.config.get('frequency', 300)) - logging.debug("Did not recieve ping back in time. Requesting Reconnect.") + log.debug("Did not recieve ping back in time. Requesting Reconnect.") self.xmpp.disconnect(reconnect=True) - + def handler_ping(self, xml): iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) iq.attrib['to'] = xml.get('from', self.xmpp.boundjid.domain) diff --git a/sleekxmpp/plugins/xep_0202.py b/sleekxmpp/plugins/xep_0202.py index c3f81b2..fe1191e 100644 --- a/sleekxmpp/plugins/xep_0202.py +++ b/sleekxmpp/plugins/xep_0202.py @@ -17,6 +17,9 @@ from .. xmlstream.matcher.xpath import MatchXPath from .. xmlstream import ElementBase, ET, JID, register_stanza_plugin +log = logging.getLogger(__name__) + + class EntityTime(ElementBase): name = 'time' namespace = 'urn:xmpp:time' @@ -84,10 +87,10 @@ class xep_0202(base.base_plugin): def handle_entity_time_query(self, iq): if iq['type'] == 'get': - logging.debug("Entity time requested by %s" % iq['from']) + log.debug("Entity time requested by %s" % iq['from']) self.xmpp.event('entity_time_request', iq) elif iq['type'] == 'result': - logging.debug("Entity time result from %s" % iq['from']) + log.debug("Entity time result from %s" % iq['from']) self.xmpp.event('entity_time', iq) def handle_entity_time(self, iq): diff --git a/sleekxmpp/stanza/rootstanza.py b/sleekxmpp/stanza/rootstanza.py index 2677ea9..6975c72 100644 --- a/sleekxmpp/stanza/rootstanza.py +++ b/sleekxmpp/stanza/rootstanza.py @@ -15,6 +15,9 @@ from sleekxmpp.stanza import Error from sleekxmpp.xmlstream import ET, StanzaBase, register_stanza_plugin +log = logging.getLogger(__name__) + + class RootStanza(StanzaBase): """ @@ -58,7 +61,7 @@ class RootStanza(StanzaBase): self['error']['text'] = "SleekXMPP got into trouble." else: self['error']['text'] = traceback.format_tb(e.__traceback__) - logging.exception('Error handling {%s}%s stanza' % + log.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) self.send() diff --git a/sleekxmpp/thirdparty/statemachine.py b/sleekxmpp/thirdparty/statemachine.py index b176df0..8a7324b 100644 --- a/sleekxmpp/thirdparty/statemachine.py +++ b/sleekxmpp/thirdparty/statemachine.py @@ -21,7 +21,7 @@ class StateMachine(object): self.addStates(states) self.__default_state = self.__states[0] self.__current_state = self.__default_state - + def addStates(self, states): self.lock.acquire() try: @@ -30,19 +30,19 @@ class StateMachine(object): raise IndexError("The state '%s' is already in the StateMachine." % state) self.__states.append(state) finally: self.lock.release() - - + + def transition(self, from_state, to_state, wait=0.0, func=None, args=[], kwargs={}): ''' - Transition from the given `from_state` to the given `to_state`. + Transition from the given `from_state` to the given `to_state`. This method will return `True` if the state machine is now in `to_state`. It - will return `False` if a timeout occurred the transition did not occur. - If `wait` is 0 (the default,) this method returns immediately if the state machine + will return `False` if a timeout occurred the transition did not occur. + If `wait` is 0 (the default,) this method returns immediately if the state machine is not in `from_state`. If you want the thread to block and transition once the state machine to enters - `from_state`, set `wait` to a non-negative value. Note there is no 'block - indefinitely' flag since this leads to deadlock. If you want to wait indefinitely, + `from_state`, set `wait` to a non-negative value. Note there is no 'block + indefinitely' flag since this leads to deadlock. If you want to wait indefinitely, choose a reasonable value for `wait` (e.g. 20 seconds) and do so in a while loop like so: :: @@ -60,42 +60,42 @@ class StateMachine(object): True value or if an exception is thrown, the transition will not occur. Any thrown exception is not caught by the state machine and is the caller's responsibility to handle. If `func` completes normally, this method will return the value returned by `func.` If - values for `args` and `kwargs` are provided, they are expanded and passed like so: + values for `args` and `kwargs` are provided, they are expanded and passed like so: `func( *args, **kwargs )`. ''' - return self.transition_any((from_state,), to_state, wait=wait, + return self.transition_any((from_state,), to_state, wait=wait, func=func, args=args, kwargs=kwargs) - - + + def transition_any(self, from_states, to_state, wait=0.0, func=None, args=[], kwargs={}): ''' Transition from any of the given `from_states` to the given `to_state`. ''' - if not (isinstance(from_states,tuple) or isinstance(from_states,list)): + if not (isinstance(from_states,tuple) or isinstance(from_states,list)): raise ValueError("from_states should be a list or tuple") for state in from_states: - if not state in self.__states: + if not state in self.__states: raise ValueError("StateMachine does not contain from_state %s." % state) - if not to_state in self.__states: + if not to_state in self.__states: raise ValueError("StateMachine does not contain to_state %s." % to_state) start = time.time() while not self.lock.acquire(False): time.sleep(.001) if (start + wait - time.time()) <= 0.0: - logging.debug("Could not acquire lock") + log.debug("Could not acquire lock") return False while not self.__current_state in from_states: # detect timeout: remainder = start + wait - time.time() - if remainder > 0: + if remainder > 0: self.notifier.wait(remainder) - else: - logging.debug("State was not ready") + else: + log.debug("State was not ready") self.lock.release() return False @@ -105,9 +105,9 @@ class StateMachine(object): # Note that func might throw an exception, but that's OK, it aborts the transition return_val = func(*args,**kwargs) if func is not None else True - # some 'false' value returned from func, + # some 'false' value returned from func, # indicating that transition should not occur: - if not return_val: return return_val + if not return_val: return return_val log.debug(' ==== TRANSITION %s -> %s', self.__current_state, to_state) self._set_state(to_state) @@ -115,7 +115,7 @@ class StateMachine(object): else: log.error("StateMachine bug!! The lock should ensure this doesn't happen!") return False - finally: + finally: self.notifier.set() # notify any waiting threads that the state has changed. self.notifier.clear() self.lock.release() @@ -125,13 +125,13 @@ class StateMachine(object): ''' Use the state machine as a context manager. The transition occurs on /exit/ from the `with` context, so long as no exception is thrown. For example: - + :: with state_machine.transition_ctx('one','two', wait=5) as locked: if locked: - # the state machine is currently locked in state 'one', and will - # transition to 'two' when the 'with' statement ends, so long as + # the state machine is currently locked in state 'one', and will + # transition to 'two' when the 'with' statement ends, so long as # no exception is thrown. print 'Currently locked in state one: %s' % state_machine['one'] @@ -142,20 +142,20 @@ class StateMachine(object): print 'Since no exception was thrown, we are now in state "two": %s' % state_machine['two'] - The other main difference between this method and `transition()` is that the - state machine is locked for the duration of the `with` statement. Normally, - after a `transition()` occurs, the state machine is immediately unlocked and + The other main difference between this method and `transition()` is that the + state machine is locked for the duration of the `with` statement. Normally, + after a `transition()` occurs, the state machine is immediately unlocked and available to another thread to call `transition()` again. ''' - if not from_state in self.__states: + if not from_state in self.__states: raise ValueError("StateMachine does not contain from_state %s." % from_state) - if not to_state in self.__states: + if not to_state in self.__states: raise ValueError("StateMachine does not contain to_state %s." % to_state) return _StateCtx(self, from_state, to_state, wait) - + def ensure(self, state, wait=0.0, block_on_transition=False): ''' Ensure the state machine is currently in `state`, or wait until it enters `state`. @@ -168,24 +168,24 @@ class StateMachine(object): Ensure we are currently in one of the given `states` or wait until we enter one of those states. - Note that due to the nature of the function, you cannot guarantee that + Note that due to the nature of the function, you cannot guarantee that the entirety of some operation completes while you remain in a given - state. That would require acquiring and holding a lock, which + state. That would require acquiring and holding a lock, which would mean no other threads could do the same. (You'd essentially be serializing all of the threads that are 'ensuring' their tasks - occurred in some state. + occurred in some state. ''' - if not (isinstance(states,tuple) or isinstance(states,list)): + if not (isinstance(states,tuple) or isinstance(states,list)): raise ValueError('states arg should be a tuple or list') for state in states: - if not state in self.__states: + if not state in self.__states: raise ValueError("StateMachine does not contain state '%s'" % state) - # if we're in the middle of a transition, determine whether we should - # 'fall back' to the 'current' state, or wait for the new state, in order to + # if we're in the middle of a transition, determine whether we should + # 'fall back' to the 'current' state, or wait for the new state, in order to # avoid an operation occurring in the wrong state. - # TODO another option would be an ensure_ctx that uses a semaphore to allow + # TODO another option would be an ensure_ctx that uses a semaphore to allow # threads to indicate they want to remain in a particular state. # will return immediately if no transition is in process. @@ -196,16 +196,16 @@ class StateMachine(object): else: self.notifier.wait() start = time.time() - while not self.__current_state in states: + while not self.__current_state in states: # detect timeout: remainder = start + wait - time.time() if remainder > 0: self.notifier.wait(remainder) else: return False return True - + def reset(self): - # TODO need to lock before calling this? + # TODO need to lock before calling this? self.transition(self.__current_state, self.__default_state) @@ -231,7 +231,7 @@ class StateMachine(object): def __str__(self): return "".join(("StateMachine(", ','.join(self.__states), "): ", self.__current_state)) - + class _StateCtx: @@ -244,28 +244,28 @@ class _StateCtx: def __enter__(self): start = time.time() - while not self.state_machine[self.from_state] or not self.state_machine.lock.acquire(False): + while not self.state_machine[self.from_state] or not self.state_machine.lock.acquire(False): # detect timeout: remainder = start + self.wait - time.time() if remainder > 0: self.state_machine.notifier.wait(remainder) - else: + else: log.debug('StateMachine timeout while waiting for state: %s', self.from_state) return False self._locked = True # lock has been acquired at this point self.state_machine.notifier.clear() - log.debug('StateMachine entered context in state: %s', + log.debug('StateMachine entered context in state: %s', self.state_machine.current_state()) return True def __exit__(self, exc_type, exc_val, exc_tb): if exc_val is not None: - log.exception("StateMachine exception in context, remaining in state: %s\n%s:%s", + log.exception("StateMachine exception in context, remaining in state: %s\n%s:%s", self.state_machine.current_state(), exc_type.__name__, exc_val) if self._locked: if exc_val is None: - log.debug(' ==== TRANSITION %s -> %s', + log.debug(' ==== TRANSITION %s -> %s', self.state_machine.current_state(), self.to_state) self.state_machine._set_state(self.to_state) diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py index 8072022..a4bc354 100644 --- a/sleekxmpp/xmlstream/handler/waiter.py +++ b/sleekxmpp/xmlstream/handler/waiter.py @@ -16,6 +16,9 @@ from sleekxmpp.xmlstream import StanzaBase, RESPONSE_TIMEOUT from sleekxmpp.xmlstream.handler.base import BaseHandler +log = logging.getLogger(__name__) + + class Waiter(BaseHandler): """ @@ -85,7 +88,7 @@ class Waiter(BaseHandler): stanza = self._payload.get(True, timeout) except queue.Empty: stanza = False - logging.warning("Timed out waiting for %s" % self.name) + log.warning("Timed out waiting for %s" % self.name) self.stream.removeHandler(self.name) return stanza diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py index 2967a2a..6ebb437 100644 --- a/sleekxmpp/xmlstream/matcher/xmlmask.py +++ b/sleekxmpp/xmlstream/matcher/xmlmask.py @@ -6,6 +6,8 @@ See the file LICENSE for copying permission. """ +import logging + from xml.parsers.expat import ExpatError from sleekxmpp.xmlstream.stanzabase import ET @@ -18,6 +20,9 @@ from sleekxmpp.xmlstream.matcher.base import MatcherBase IGNORE_NS = False +log = logging.getLogger(__name__) + + class MatchXMLMask(MatcherBase): """ @@ -97,8 +102,7 @@ class MatchXMLMask(MatcherBase): try: mask = ET.fromstring(mask) except ExpatError: - logging.log(logging.WARNING, - "Expat error: %s\nIn parsing: %s" % ('', mask)) + log.warning("Expat error: %s\nIn parsing: %s" % ('', mask)) if not use_ns: # Compare the element without using namespaces. diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py index 240d4a4..e0324cb 100644 --- a/sleekxmpp/xmlstream/scheduler.py +++ b/sleekxmpp/xmlstream/scheduler.py @@ -15,6 +15,9 @@ except ImportError: import Queue as queue +log = logging.getLogger(__name__) + + class Task(object): """ @@ -168,13 +171,13 @@ class Scheduler(object): except KeyboardInterrupt: self.run = False if self.parentstop is not None: - logging.debug("stopping parent") + log.debug("stopping parent") self.parentstop.set() except SystemExit: self.run = False if self.parentstop is not None: self.parentstop.set() - logging.debug("Quitting Scheduler thread") + log.debug("Quitting Scheduler thread") if self.parentqueue is not None: self.parentqueue.put(('quit', None, None)) diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py index f4d66aa..aabd386 100644 --- a/sleekxmpp/xmlstream/stanzabase.py +++ b/sleekxmpp/xmlstream/stanzabase.py @@ -16,6 +16,9 @@ from sleekxmpp.xmlstream import JID from sleekxmpp.xmlstream.tostring import tostring +log = logging.getLogger(__name__) + + # Used to check if an argument is an XML object. XML_TYPE = type(ET.Element('xml')) @@ -1140,7 +1143,7 @@ class StanzaBase(ElementBase): Meant to be overridden. """ - logging.exception('Error handling {%s}%s stanza' % (self.namespace, + log.exception('Error handling {%s}%s stanza' % (self.namespace, self.name)) def send(self): diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py index 85619db..30b76ce 100644 --- a/sleekxmpp/xmlstream/xmlstream.py +++ b/sleekxmpp/xmlstream/xmlstream.py @@ -44,6 +44,9 @@ HANDLER_THREADS = 1 SSL_SUPPORT = True +log = logging.getLogger(__name__) + + class RestartStream(Exception): """ Exception to restart stream processing, including @@ -206,7 +209,7 @@ class XMLStream(object): # Used in Windows signal.signal(signal.SIGTERM, self._handle_kill) except: - logging.debug("Can not set interrupt signal handlers. " + \ + log.debug("Can not set interrupt signal handlers. " + \ "SleekXMPP is not running from a main thread.") def _handle_kill(self, signum, frame): @@ -275,7 +278,7 @@ class XMLStream(object): self.socket = self.socket_class(Socket.AF_INET, Socket.SOCK_STREAM) self.socket.settimeout(None) if self.use_ssl and self.ssl_support: - logging.debug("Socket Wrapped for SSL") + log.debug("Socket Wrapped for SSL") ssl_socket = ssl.wrap_socket(self.socket) if hasattr(self.socket, 'socket'): # We are using a testing socket, so preserve the top @@ -285,7 +288,7 @@ class XMLStream(object): self.socket = ssl_socket try: - logging.debug("Connecting to %s:%s" % self.address) + log.debug("Connecting to %s:%s" % self.address) self.socket.connect(self.address) self.set_socket(self.socket, ignore=True) #this event is where you should set your application state @@ -293,7 +296,7 @@ class XMLStream(object): return True except Socket.error as serr: error_msg = "Could not connect to %s:%s. Socket Error #%s: %s" - logging.error(error_msg % (self.address[0], self.address[1], + log.error(error_msg % (self.address[0], self.address[1], serr.errno, serr.strerror)) time.sleep(1) return False @@ -338,10 +341,10 @@ class XMLStream(object): """ Reset the stream's state and reconnect to the server. """ - logging.debug("reconnecting...") + log.debug("reconnecting...") self.state.transition('connected', 'disconnected', wait=2.0, func=self._disconnect, args=(True,)) - logging.debug("connecting...") + log.debug("connecting...") return self.state.transition('disconnected', 'connected', wait=2.0, func=self._connect) @@ -378,8 +381,8 @@ class XMLStream(object): to be restarted. """ if self.ssl_support: - logging.info("Negotiating TLS") - logging.info("Using SSL version: %s" % str(self.ssl_version)) + log.info("Negotiating TLS") + log.info("Using SSL version: %s" % str(self.ssl_version)) ssl_socket = ssl.wrap_socket(self.socket, ssl_version=self.ssl_version, do_handshake_on_connect=False) @@ -393,7 +396,7 @@ class XMLStream(object): self.set_socket(self.socket) return True else: - logging.warning("Tried to enable TLS, but ssl module not found.") + log.warning("Tried to enable TLS, but ssl module not found.") return False def start_stream_handler(self, xml): @@ -547,7 +550,7 @@ class XMLStream(object): name -- The name of the event to trigger. data -- Data that will be passed to each event handler. Defaults to an empty dictionary. - direct -- Runs the event directly if True, skipping the + direct -- Runs the event directly if True, skipping the event queue. All event handlers will run in the same thread. """ @@ -557,7 +560,7 @@ class XMLStream(object): handler[0](copy.copy(data)) except Exception as e: error_msg = 'Error processing event handler: %s' - logging.exception(error_msg % str(handler[0])) + log.exception(error_msg % str(handler[0])) if hasattr(data, 'exception'): data.exception(e) else: @@ -622,7 +625,7 @@ class XMLStream(object): mask = mask.xml data = str(data) if mask is not None: - logging.warning("Use of send mask waiters is deprecated.") + log.warning("Use of send mask waiters is deprecated.") wait_for = Waiter("SendWait_%s" % self.new_id(), MatchXMLMask(mask)) self.register_handler(wait_for) @@ -679,7 +682,7 @@ class XMLStream(object): self.__thread[name].start() for t in range(0, HANDLER_THREADS): - logging.debug("Starting HANDLER THREAD") + log.debug("Starting HANDLER THREAD") start_thread('stream_event_handler_%s' % t, self._event_runner) start_thread('send_thread', self._send_thread) @@ -717,16 +720,16 @@ class XMLStream(object): if self.is_client: self.send_raw(self.stream_header) except KeyboardInterrupt: - logging.debug("Keyboard Escape Detected in _process") + log.debug("Keyboard Escape Detected in _process") self.stop.set() except SystemExit: - logging.debug("SystemExit in _process") + log.debug("SystemExit in _process") self.stop.set() except Socket.error: - logging.exception('Socket Error') + log.exception('Socket Error') except: if not self.stop.isSet(): - logging.exception('Connection error.') + log.exception('Connection error.') if not self.stop.isSet() and self.auto_reconnect: self.reconnect() else: @@ -756,7 +759,7 @@ class XMLStream(object): if depth == 0: # The stream's root element has closed, # terminating the stream. - logging.debug("End of stream recieved") + log.debug("End of stream recieved") self.stream_end_event.set() return False elif depth == 1: @@ -770,7 +773,7 @@ class XMLStream(object): # Keep the root element empty of children to # save on memory use. root.clear() - logging.debug("Ending read XML loop") + log.debug("Ending read XML loop") def _build_stanza(self, xml, default_ns=None): """ @@ -781,7 +784,7 @@ class XMLStream(object): Arguments: xml -- The XML object to convert into a stanza object. - default_ns -- Optional default namespace to use instead of the + default_ns -- Optional default namespace to use instead of the stream's current default namespace. """ if default_ns is None: @@ -803,7 +806,7 @@ class XMLStream(object): Arguments: xml -- The XML stanza to analyze. """ - logging.debug("RECV: %s" % tostring(xml, + log.debug("RECV: %s" % tostring(xml, xmlns=self.default_ns, stream=self)) # Apply any preprocessing filters. @@ -852,7 +855,7 @@ class XMLStream(object): func(*args) except Exception as e: error_msg = 'Error processing event handler: %s' - logging.exception(error_msg % str(func)) + log.exception(error_msg % str(func)) if hasattr(args[0], 'exception'): args[0].exception(e) @@ -865,7 +868,7 @@ class XMLStream(object): Stream event handlers will all execute in this thread. Custom event handlers may be spawned in individual threads. """ - logging.debug("Loading event runner") + log.debug("Loading event runner") try: while not self.stop.isSet(): try: @@ -883,14 +886,14 @@ class XMLStream(object): handler.run(args[0]) except Exception as e: error_msg = 'Error processing stream handler: %s' - logging.exception(error_msg % handler.name) + log.exception(error_msg % handler.name) args[0].exception(e) elif etype == 'schedule': try: - logging.debug(args) + log.debug(args) handler(*args[0]) except: - logging.exception('Error processing scheduled task') + log.exception('Error processing scheduled task') elif etype == 'event': func, threaded, disposable = handler try: @@ -904,14 +907,14 @@ class XMLStream(object): func(*args) except Exception as e: error_msg = 'Error processing event handler: %s' - logging.exception(error_msg % str(func)) + log.exception(error_msg % str(func)) if hasattr(args[0], 'exception'): args[0].exception(e) elif etype == 'quit': - logging.debug("Quitting event runner thread") + log.debug("Quitting event runner thread") return False except KeyboardInterrupt: - logging.debug("Keyboard Escape Detected in _event_runner") + log.debug("Keyboard Escape Detected in _event_runner") self.disconnect() return except SystemExit: @@ -929,14 +932,14 @@ class XMLStream(object): data = self.send_queue.get(True, 1) except queue.Empty: continue - logging.debug("SEND: %s" % data) + log.debug("SEND: %s" % data) try: self.socket.send(data.encode('utf-8')) except: - logging.warning("Failed to send %s" % data) + log.warning("Failed to send %s" % data) self.disconnect(self.auto_reconnect) except KeyboardInterrupt: - logging.debug("Keyboard Escape Detected in _send_thread") + log.debug("Keyboard Escape Detected in _send_thread") self.disconnect() return except SystemExit: From 9dbf246f0bfcac2c4ce538431ee629f126c86447 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Mon, 8 Nov 2010 09:14:17 +0800 Subject: [PATCH 11/20] Doesn't fail if host has NO SRV record Just catch an other exception type coming from the dns resolver that could be raised with hosts like "anon.example.com" which just don't have any SRV record. --- sleekxmpp/basexmpp.py | 9 --------- sleekxmpp/clientxmpp.py | 2 +- sleekxmpp/componentxmpp.py | 2 +- 3 files changed, 2 insertions(+), 11 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index 77191e7..cd7d251 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -28,15 +28,6 @@ from sleekxmpp.xmlstream.handler import * log = logging.getLogger(__name__) - -# Flag indicating if DNS SRV records are available for use. -SRV_SUPPORT = True -try: - import dns.resolver -except: - SRV_SUPPORT = False - - # In order to make sure that Unicode is handled properly # in Python 2.x, reset the default encoding. if sys.version_info < (3, 0): diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index a88c5cc..1c60081 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -164,7 +164,7 @@ class ClientXMPP(BaseXMPP): try: xmpp_srv = "_xmpp-client._tcp.%s" % self.server answers = dns.resolver.query(xmpp_srv, dns.rdatatype.SRV) - except dns.resolver.NXDOMAIN: + except (dns.resolver.NXDOMAIN, dns.resolver.NoAnswer): log.debug("No appropriate SRV record found." + \ " Using JID server name.") else: diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index 8b0b6cc..ae58c5f 100644 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -15,7 +15,7 @@ import hashlib from sleekxmpp import plugins from sleekxmpp import stanza -from sleekxmpp.basexmpp import BaseXMPP, SRV_SUPPORT +from sleekxmpp.basexmpp import BaseXMPP from sleekxmpp.xmlstream import XMLStream, RestartStream from sleekxmpp.xmlstream import StanzaBase, ET from sleekxmpp.xmlstream.matcher import * From b73a8590310c61c8d56b6a6f861c8666b4e71064 Mon Sep 17 00:00:00 2001 From: Florent Le Coz Date: Wed, 10 Nov 2010 04:56:38 +0800 Subject: [PATCH 12/20] Add a groupchat_subject event Use this event to get notified of the subject changes (or to get the subject of the room when joining one) --- sleekxmpp/plugins/xep_0045.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sleekxmpp/plugins/xep_0045.py b/sleekxmpp/plugins/xep_0045.py index de98235..db41cdb 100644 --- a/sleekxmpp/plugins/xep_0045.py +++ b/sleekxmpp/plugins/xep_0045.py @@ -120,6 +120,7 @@ class xep_0045(base.base_plugin): registerStanzaPlugin(Presence, MUCPresence) self.xmpp.registerHandler(Callback('MUCPresence', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_presence)) self.xmpp.registerHandler(Callback('MUCMessage', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_message)) + self.xmpp.registerHandler(Callback('MUCSubject', MatchXMLMask("" % self.xmpp.default_ns), self.handle_groupchat_subject)) def handle_groupchat_presence(self, pr): """ Handle a presence in a muc. @@ -153,6 +154,12 @@ class xep_0045(base.base_plugin): self.xmpp.event('groupchat_message', msg) self.xmpp.event("muc::%s::message" % msg['from'].bare, msg) + def handle_groupchat_subject(self, msg): + """ Handle a message coming from a muc indicating + a change of subject (or announcing it when joining the room) + """ + self.xmpp.event('groupchat_subject', msg) + def jidInRoom(self, room, jid): for nick in self.rooms[room]: entry = self.rooms[room][nick] From b8f40eb8431dd0965d6ed3fd61956f4c91729202 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 16 Nov 2010 17:43:05 -0800 Subject: [PATCH 13/20] xep_0199 ping now uses scheduler instead of dedicated thread --- sleekxmpp/plugins/xep_0199.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sleekxmpp/plugins/xep_0199.py b/sleekxmpp/plugins/xep_0199.py index 2005594..2e99ae7 100644 --- a/sleekxmpp/plugins/xep_0199.py +++ b/sleekxmpp/plugins/xep_0199.py @@ -21,7 +21,6 @@ class xep_0199(base.base_plugin): self.description = "XMPP Ping" self.xep = "0199" self.xmpp.add_handler("" % self.xmpp.default_ns, self.handler_ping, name='XMPP Ping') - self.running = False if self.config.get('keepalive', True): self.xmpp.add_event_handler('session_start', self.handler_pingserver, threaded=True) @@ -30,12 +29,13 @@ class xep_0199(base.base_plugin): self.xmpp.plugin['xep_0030'].add_feature('urn:xmpp:ping') def handler_pingserver(self, xml): - if not self.running: - time.sleep(self.config.get('frequency', 300)) - while self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is not False: - time.sleep(self.config.get('frequency', 300)) + self.xmpp.schedule("xep-0119 ping", float(self.config.get('frequency', 300)), self.scheduled_ping, repeat=True) + + def scheduled_ping(self): + log.debug("pinging...") + if self.sendPing(self.xmpp.server, self.config.get('timeout', 30)) is False: log.debug("Did not recieve ping back in time. Requesting Reconnect.") - self.xmpp.disconnect(reconnect=True) + self.xmpp.reconnect() def handler_ping(self, xml): iq = self.xmpp.makeIqResult(xml.get('id', 'unknown')) From 45991e47eeab97f0411139c5b1627ac6350de3d0 Mon Sep 17 00:00:00 2001 From: Nathan Fritz Date: Tue, 16 Nov 2010 17:58:20 -0800 Subject: [PATCH 14/20] scheduler no longer waits for the next event before exiting --- sleekxmpp/xmlstream/scheduler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py index e0324cb..1435910 100644 --- a/sleekxmpp/xmlstream/scheduler.py +++ b/sleekxmpp/xmlstream/scheduler.py @@ -149,6 +149,8 @@ class Scheduler(object): if wait <= 0.0: newtask = self.addq.get(False) else: + if wait >= 3.0: + wait = 3.0 newtask = self.addq.get(True, wait) except queue.Empty: cleanup = [] From 673545c7e48d86b02f811ad239ed317e4bca0bbc Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Tue, 26 Oct 2010 23:47:17 -0400 Subject: [PATCH 15/20] First pass at integrating the new roster manager. --- sleekxmpp/basexmpp.py | 149 +++++--------- sleekxmpp/clientxmpp.py | 17 +- sleekxmpp/componentxmpp.py | 4 +- sleekxmpp/roster.py | 374 ++++++++++++++++++++++++++++++++++ sleekxmpp/stanza/roster.py | 2 + sleekxmpp/test/sleektest.py | 27 +++ tests/test_stanza_roster.py | 4 + tests/test_stream_presence.py | 34 +++- tests/test_stream_roster.py | 40 ++-- 9 files changed, 514 insertions(+), 137 deletions(-) create mode 100644 sleekxmpp/roster.py diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index cd7d251..fb718fb 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -15,6 +15,7 @@ import logging import sleekxmpp from sleekxmpp import plugins +from sleekxmpp.roster import MultiRoster from sleekxmpp.stanza import Message, Presence, Iq, Error from sleekxmpp.stanza.roster import Roster from sleekxmpp.stanza.nick import Nick @@ -78,7 +79,7 @@ class BaseXMPP(XMLStream): send_presence_subscribe -- Send a subscription request. """ - def __init__(self, default_ns='jabber:client'): + def __init__(self, jid='', default_ns='jabber:client'): """ Adapt an XML stream for use with XMPP. @@ -107,10 +108,13 @@ class BaseXMPP(XMLStream): self.default_ns = default_ns self.stream_ns = 'http://etherx.jabber.org/streams' - self.boundjid = JID("") + self.boundjid = JID(jid) self.plugin = {} + self.rosters = MultiRoster(self) + self.rosters.add(self.boundjid.bare) self.roster = {} + self.is_component = False self.auto_authorize = True self.auto_subscribe = True @@ -127,10 +131,20 @@ class BaseXMPP(XMLStream): MatchXPath("{%s}presence" % self.default_ns), self._handle_presence)) - self.add_event_handler('presence_subscribe', - self._handle_subscribe) self.add_event_handler('disconnected', self._handle_disconnected) + self.add_event_handler('presence_available', self._handle_available) + self.add_event_handler('presence_dnd', self._handle_available) + self.add_event_handler('presence_xa', self._handle_available) + self.add_event_handler('presence_chat', self._handle_available) + self.add_event_handler('presence_away', self._handle_available) + self.add_event_handler('presence_unavailable', self._handle_unavailable) + self.add_event_handler('presence_subscribe', self._handle_subscribe) + self.add_event_handler('presence_subscribed', self._handle_subscribed) + self.add_event_handler('presence_unsubscribe', self._handle_unsubscribe) + self.add_event_handler('presence_unsubscribed', self._handle_unsubscribed) + self.add_event_handler('presence_probe', self._handle_probe) + self.add_event_handler('roster_subscription_request', self._handle_new_subscription) # Set up the XML stream with XMPP's root stanzas. self.registerStanza(Message) @@ -522,12 +536,49 @@ class BaseXMPP(XMLStream): """Process incoming message stanzas.""" self.event('message', msg) + def _handle_available(self, presence): + self.rosters[presence['to'].bare][presence['from'].bare].handle_available(presence) + + def _handle_unavailable(self, presence): + self.rosters[presence['to'].bare][presence['from'].bare].handle_unavailable(presence) + + def _handle_new_subscription(self, stanza): + roster = self.rosters[stanza['to'].bare] + item = self.rosters[stanza['to'].bare][stanza['from'].bare] + if item['whitelisted']: + item.authorize() + elif roster.auto_authorize: + item.authorize() + if roster.auto_subscribe: + item.subscribe() + elif roster.auto_authorize == False: + item.unauthorize() + + def _handle_removed_subscription(self, presence): + self.rosters[presence['to'].bare][presence['from'].bare].unauthorize() + + def _handle_subscribe(self, stanza): + self.rosters[stanza['to'].bare][stanza['from'].bare].handle_subscribe(stanza) + + def _handle_subscribed(self, stanza): + self.rosters[stanza['to'].bare][stanza['from'].bare].handle_subscribed(stanza) + + def _handle_unsubscribe(self, stanza): + self.rosters[stanza['to'].bare][stanza['from'].bare].handle_unsubscribe(stanza) + + def _handle_unsubscribed(self, stanza): + self.rosters[stanza['to'].bare][stanza['from'].bare].handle_unsubscribed(stanza) + + def _handle_probe(self, stanza): + self.rosteritems[stanza['to'].bare][stanza['from'].bare].handle_probe(stanza) + def _handle_presence(self, presence): """ Process incoming presence stanzas. Update the roster with presence information. """ + logging.debug(presence['type']) self.event("presence_%s" % presence['type'], presence) # Check for changes in subscription state. @@ -538,97 +589,7 @@ class BaseXMPP(XMLStream): elif not presence['type'] in ('available', 'unavailable') and \ not presence['type'] in presence.showtypes: return - - # Strip the information from the stanza. - jid = presence['from'].bare - resource = presence['from'].resource - show = presence['type'] - status = presence['status'] - priority = presence['priority'] - - was_offline = False - got_online = False - old_roster = self.roster.get(jid, {}).get(resource, {}) - - # Create a new roster entry if needed. - if not jid in self.roster: - self.roster[jid] = {'groups': [], - 'name': '', - 'subscription': 'none', - 'presence': {}, - 'in_roster': False} - - # Alias to simplify some references. - connections = self.roster[jid]['presence'] - - # Determine if the user has just come online. - if not resource in connections: - if show == 'available' or show in presence.showtypes: - got_online = True - was_offline = True - connections[resource] = {} - - if connections[resource].get('show', 'unavailable') == 'unavailable': - was_offline = True - - # Update the roster's state for this JID's resource. - connections[resource] = {'show': show, - 'status': status, - 'priority': priority} - - name = self.roster[jid].get('name', '') - - # Remove unneeded state information after a resource - # disconnects. Determine if this was the last connection - # for the JID. - if show == 'unavailable': - log.debug("%s %s got offline" % (jid, resource)) - del connections[resource] - - if not connections and not self.roster[jid]['in_roster']: - del self.roster[jid] - if not was_offline: - self.event("got_offline", presence) - else: - return False - - name = '(%s) ' % name if name else '' - - # Presence state has changed. self.event("changed_status", presence) - if got_online: - self.event("got_online", presence) - log.debug("STATUS: %s%s/%s[%s]: %s" % (name, jid, resource, - show, status)) - - def _handle_subscribe(self, presence): - """ - Automatically managage subscription requests. - - Subscription behavior is controlled by the settings - self.auto_authorize and self.auto_subscribe. - - auto_auth auto_sub Result: - True True Create bi-directional subsriptions. - True False Create only directed subscriptions. - False * Decline all subscriptions. - None * Disable automatic handling and use - a custom handler. - """ - presence.reply() - presence['to'] = presence['to'].bare - - # We are using trinary logic, so conditions have to be - # more explicit than usual. - if self.auto_authorize == True: - presence['type'] = 'subscribed' - presence.send() - if self.auto_subscribe: - presence['type'] = 'subscribe' - presence.send() - elif self.auto_authorize == False: - presence['type'] = 'unsubscribed' - presence.send() # Restore the old, lowercased name for backwards compatibility. basexmpp = BaseXMPP diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 1c60081..8b8f05f 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -66,7 +66,7 @@ class ClientXMPP(BaseXMPP): when calling register_plugins. escape_quotes -- Deprecated. """ - BaseXMPP.__init__(self, 'jabber:client') + BaseXMPP.__init__(self, jid, 'jabber:client') # To comply with PEP8, method names now use underscores. # Deprecated method names are re-mapped for backwards compatibility. @@ -75,7 +75,6 @@ class ClientXMPP(BaseXMPP): self.getRoster = self.get_roster self.registerFeature = self.register_feature - self.set_jid(jid) self.password = password self.escape_quotes = escape_quotes self.plugin_config = plugin_config @@ -421,13 +420,13 @@ class ClientXMPP(BaseXMPP): """ if iq['type'] == 'set' or (iq['type'] == 'result' and request): for jid in iq['roster']['items']: - if not jid in self.roster: - self.roster[jid] = {'groups': [], - 'name': '', - 'subscription': 'none', - 'presence': {}, - 'in_roster': True} - self.roster[jid].update(iq['roster']['items'][jid]) + item = iq['roster']['items'][jid] + roster = self.rosters[iq['to'].bare] + roster[jid]['name'] = item['name'] + roster[jid]['groups'] = item['groups'] + roster[jid]['from'] = item['subscription'] in ['from', 'both'] + roster[jid]['to'] = item['subscription'] in ['to', 'both'] + roster[jid]['pending_out'] = (item['ask'] == 'subscribe') self.event("roster_update", iq) if iq['type'] == 'set': diff --git a/sleekxmpp/componentxmpp.py b/sleekxmpp/componentxmpp.py index ae58c5f..0963c50 100644 --- a/sleekxmpp/componentxmpp.py +++ b/sleekxmpp/componentxmpp.py @@ -58,7 +58,7 @@ class ComponentXMPP(BaseXMPP): default_ns = 'jabber:client' else: default_ns = 'jabber:component:accept' - BaseXMPP.__init__(self, default_ns) + BaseXMPP.__init__(self, jid, default_ns) self.auto_authorize = None self.stream_header = "" % ( @@ -68,8 +68,8 @@ class ComponentXMPP(BaseXMPP): self.stream_footer = "" self.server_host = host self.server_port = port - self.set_jid(jid) self.secret = secret + self.plugin_config = plugin_config self.plugin_whitelist = plugin_whitelist self.is_component = True diff --git a/sleekxmpp/roster.py b/sleekxmpp/roster.py new file mode 100644 index 0000000..32fd5c0 --- /dev/null +++ b/sleekxmpp/roster.py @@ -0,0 +1,374 @@ +import logging + + +class MultiRoster(object): + + def __init__(self, xmpp, datastore=None): + self.xmpp = xmpp + self.datastore = datastore + self._rosters = {} + + def __getitem__(self, key): + if key not in self._rosters: + self.add(key) + return self._rosters[key] + + def keys(self): + return self._rosters.keys() + + def __iter__(self): + return self._rosters.__iter__() + + def add(self, node): + if node not in self._rosters: + self._rosters[node] = Roster(self.xmpp, node, self.datastore) + +class Roster(object): + + def __init__(self, xmpp, jid, datastore=None): + self.xmpp = xmpp + self.jid = jid + self.datastore = datastore + self.auto_authorize = True + self.auto_subscribe = True + self._jids = {} + + def __getitem__(self, key): + if key not in self._jids: + self.add(key, save=True) + return self._jids[key] + + def keys(self): + return self._jids.keys() + + def __iter__(self): + return self._jids.__iter__() + + def add(self, jid, name='', groups=None, afrom=False, ato=False, + pending_in=False, pending_out=False, whitelisted=False, + save=False): + state = {'name': name, + 'groups': groups or [], + 'from': afrom, + 'to': ato, + 'pending_in': pending_in, + 'pending_out': pending_out, + 'whitelisted': whitelisted, + 'subscription': 'none'} + self._jids[jid] = RosterItem(self.xmpp, jid, self.jid, + state=state, datastore=self.datastore) + if save: + self._jids[jid].save() + + def subscribe(self, jid): + self._jids[jid].subscribe() + + def unsubscribe(self, jid): + self._jids[jid].unsubscribe() + + def remove(self, jid): + self._jids[jid].remove() + if not self.xmpp.is_component: + self.update(jid, subscription='remove') + + def update(self, jid, name=None, subscription=None, groups=[]): + self._jids[jid]['name'] = name + self._jids[jid]['groups'] = group + self._jids[jid].save() + + if not self.xmpp.is_component: + iq = self.Iq() + iq['type'] = 'set' + iq['roster']['items'] = {jid: {'name': name, + 'subscription': subscription, + 'groups': groups}} + response = iq.send() + return response and response['type'] == 'result' + + def presence(self, jid, resource=None): + if resource is None: + return self._jids[jid].resources + + default_presence = {'status': '', + 'priority': 0, + 'show': ''} + return self._jids[jid].resources.get(resource, + default_presence) + + +class RosterItem(object): + + def __init__(self, xmpp, jid, owner=None, + state=None, datastore=None): + self.xmpp = xmpp + self.jid = jid + self.owner = owner or self.xmpp.jid + self.last_status = None + self.resources = {} + self.datastore = datastore + + self._state = state or { + 'from': False, + 'to': False, + 'pending_in': False, + 'pending_out': False, + 'whitelisted': False, + 'subscription': 'none', + 'name': '', + 'groups': []} + self._datastore_state = {} + self.load() + + def load(self): + if self.datastore: + item = self.datastore.load(self.owner, self.jid, + self._datastore_state) + if item: + self['name'] = item['name'] + self['groups'] = item['groups'] + self['from'] = item['from'] + self['to'] = item['to'] + self['whitelisted'] = item['whitelisted'] + self['pending_out'] = item['pending_out'] + self['pending_in'] = item['pending_in'] + self['subscription'] = self._subscription() + return self._state + return None + + def save(self): + if self.datastore: + self.datastore.save(self.owner, self.jid, + self._state, self._datastore_state) + + def __getitem__(self, key): + if key in self._state: + if key == 'subscription': + return self._subscription() + return self._state[key] + else: + raise KeyError + + def __setitem__(self, key, value): + print "%s: %s" % (key, value) + if key in self._state: + if key in ['name', 'subscription', 'groups']: + self._state[key] = value + else: + value = str(value).lower() + self._state[key] = value in ('true', '1', 'on', 'yes') + else: + raise KeyError + + def _subscription(self): + if self['to'] and self['from']: + return 'both' + elif self['from']: + return 'from' + elif self['to']: + return 'to' + else: + return 'none' + + def remove(self): + "Remove the jids subscription, inform it if it is subscribed, and unwhitelist it" + if self['to']: + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = ['unsubscribe'] + if self.xmpp.is_component: + p['from'] = self.owner + p.send() + self['to'] = False + self['whitelisted'] = False + self.save() + + def subscribe(self): + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'subscribe' + if self.xmpp.is_component: + p['from'] = self.owner + self['pending_out'] = True + self.save() + p.send() + + def authorize(self): + self['from'] = True + self['pending_in'] = False + self.save() + self._subscribed() + self.send_last_presence() + + def unauthorize(self): + self['from'] = False + self['pending_in'] = False + self.save() + self._unsubscribed() + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'unavailable' + if self.xmpp.is_component: + p['from'] = self.owner + p.send() + + def _subscribed(self): + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'subscribed' + if self.xmpp.is_component: + p['from'] = self.owner + p.send() + + def unsubscribe(self): + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'unsubscribe' + if self.xmpp.is_component: + p['from'] = self.owner + self.save() + p.send() + + def _unsubscribed(self): + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = 'unsubscribed' + if self.xmpp.is_component: + p['from'] = self.owner + p.send() + + def send_presence(self, ptype='available', status=None): + p = self.xmpp.Presence() + p['to'] = self.jid + p['type'] = ptype + p['status'] = status + if self.xmpp.is_component: + p['from'] = self.owner + self.last_status = p + p.send() + + def send_last_presence(self): + if self.last_status is None: + self.send_presence() + else: + self.last_status.send() + + def handle_available(self, presence): + resource = presence['from'].resource + data = {'status': presence['status'], + 'show': presence['show'], + 'priority': presence['priority']} + if not self.resources: + self.xmpp.event('got_online', presence) + if resource not in self.resources: + self.resources[resource] = {} + self.resources[resource].update(data) + + def handle_unavailable(self, presence): + resource = presence['from'].resource + if not self.resources: + return + if resource in self.resources: + del self.resources[resource] + if not self.resources: + self.xmpp.event('got_offline', presence) + + def handle_subscribe(self, presence): + """ + +------------------------------------------------------------------+ + | EXISTING STATE | DELIVER? | NEW STATE | + +------------------------------------------------------------------+ + | "None" | yes | "None + Pending In" | + | "None + Pending Out" | yes | "None + Pending Out/In" | + | "None + Pending In" | no | no state change | + | "None + Pending Out/In" | no | no state change | + | "To" | yes | "To + Pending In" | + | "To + Pending In" | no | no state change | + | "From" | no * | no state change | + | "From + Pending Out" | no * | no state change | + | "Both" | no * | no state change | + +------------------------------------------------------------------+ + """ + if not self['from'] and not self['pending_in']: + self['pending_in'] = True + self.xmpp.event('roster_subscription_request', presence) + elif self['from']: + self._subscribed() + self.save() + + def handle_subscribed(self, presence): + """ + +------------------------------------------------------------------+ + | EXISTING STATE | DELIVER? | NEW STATE | + +------------------------------------------------------------------+ + | "None" | no | no state change | + | "None + Pending Out" | yes | "To" | + | "None + Pending In" | no | no state change | + | "None + Pending Out/In" | yes | "To + Pending In" | + | "To" | no | no state change | + | "To + Pending In" | no | no state change | + | "From" | no | no state change | + | "From + Pending Out" | yes | "Both" | + | "Both" | no | no state change | + +------------------------------------------------------------------+ + """ + if not self['to'] and self['pending_out']: + self['pending_out'] = False + self['to'] = True + self.xmpp.event('roster_subscription_authorized', presence) + self.save() + + def handle_unsubscribe(self, presence): + """ + +------------------------------------------------------------------+ + | EXISTING STATE | DELIVER? | NEW STATE | + +------------------------------------------------------------------+ + | "None" | no | no state change | + | "None + Pending Out" | no | no state change | + | "None + Pending In" | yes * | "None" | + | "None + Pending Out/In" | yes * | "None + Pending Out" | + | "To" | no | no state change | + | "To + Pending In" | yes * | "To" | + | "From" | yes * | "None" | + | "From + Pending Out" | yes * | "None + Pending Out | + | "Both" | yes * | "To" | + +------------------------------------------------------------------+ + """ + if not self['from'] and self['pending_in']: + self['pending_in'] = False + self._unsubscribed() + elif self['from']: + self['from'] = False + self._unsubscribed() + self.xmpp.event('roster_subscription_remove', presence) + self.save() + + def handle_unsubscribed(self, presence): + """ + +------------------------------------------------------------------+ + | EXISTING STATE | DELIVER? | NEW STATE | + +------------------------------------------------------------------+ + | "None" | no | no state change | + | "None + Pending Out" | yes | "None" | + | "None + Pending In" | no | no state change | + | "None + Pending Out/In" | yes | "None + Pending In" | + | "To" | yes | "None" | + | "To + Pending In" | yes | "None + Pending In" | + | "From" | no | no state change | + | "From + Pending Out" | yes | "From" | + | "Both" | yes | "From" | + +------------------------------------------------------------------ + """ + if not self['to'] and self['pending_out']: + self['pending_out'] = False + elif self['to'] and not self['pending_out']: + self['to'] = False + self.xmpp.event('roster_subscription_removed', presence) + self.save() + + def handle_probe(self, presence): + if self['to']: + self.send_last_presence() + if self['pending_out']: + self.subscribe() + if not self['to']: + self._unsubscribed() diff --git a/sleekxmpp/stanza/roster.py b/sleekxmpp/stanza/roster.py index 8f154a2..57ea62b 100644 --- a/sleekxmpp/stanza/roster.py +++ b/sleekxmpp/stanza/roster.py @@ -106,6 +106,8 @@ class Roster(ElementBase): item = {} item['name'] = itemxml.get('name', '') item['subscription'] = itemxml.get('subscription', '') + item['ask'] = itemxml.get('ask', '') + item['approved'] = itemxml.get('approved', '') item['groups'] = [] groupsxml = itemxml.findall('{jabber:iq:roster}group') if groupsxml is not None: diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index f8b4b54..c481d73 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -137,6 +137,33 @@ class SleekTest(unittest.TestCase): self.assertEqual(str(jid), string, "String does not match: %s" % str(jid)) + def check_roster(self, owner, jid, name=None, subscription=None, + afrom=None, ato=None, pending_out=None, pending_in=None, + groups=None): + roster = self.xmpp.rosters[owner][jid] + print roster._state + if name is not None: + self.assertEqual(roster['name'], name, + "Incorrect name value: %s" % roster['name']) + if subscription is not None: + self.assertEqual(roster['subscription'], subscription, + "Incorrect subscription: %s" % roster['subscription']) + if afrom is not None: + self.assertEqual(roster['from'], afrom, + "Incorrect from state: %s" % roster['from']) + if ato is not None: + self.assertEqual(roster['to'], ato, + "Incorrect to state: %s" % roster['to']) + if pending_out is not None: + self.assertEqual(roster['pending_out'], pending_out, + "Incorrect pending_out state: %s" % roster['pending_out']) + if pending_in is not None: + self.assertEqual(roster['pending_in'], pending_out, + "Incorrect pending_in state: %s" % roster['pending_in']) + if groups is not None: + self.assertEqual(roster['groups'], groups, + "Incorrect groups: %s" % roster['groups']) + # ------------------------------------------------------------------ # Methods for comparing stanza objects to XML strings diff --git a/tests/test_stanza_roster.py b/tests/test_stanza_roster.py index cd3e607..8ec2d32 100644 --- a/tests/test_stanza_roster.py +++ b/tests/test_stanza_roster.py @@ -48,10 +48,14 @@ class TestRosterStanzas(SleekTest): 'user@example.com': { 'name': 'User', 'subscription': 'both', + 'ask': '', + 'approved': '', 'groups': ['Friends', 'Coworkers']}, 'otheruser@example.com': { 'name': 'Other User', 'subscription': 'both', + 'ask': '', + 'approved': '', 'groups': []}} debug = "Roster items don't match after retrieval." debug += "\nReturned: %s" % str(iq['roster']['items']) diff --git a/tests/test_stream_presence.py b/tests/test_stream_presence.py index 1d5caa9..8ca1c0e 100644 --- a/tests/test_stream_presence.py +++ b/tests/test_stream_presence.py @@ -30,7 +30,9 @@ class TestStreamPresence(SleekTest): self.xmpp.add_event_handler('presence_unavailable', unavailable) self.recv(""" - + """) # Give event queue time to process. @@ -68,12 +70,14 @@ class TestStreamPresence(SleekTest): # Contact comes online. self.recv(""" - + """) # Contact goes offline, should trigger got_offline. self.recv(""" """) @@ -99,7 +103,8 @@ class TestStreamPresence(SleekTest): self.xmpp.add_event_handler('got_online', got_online) self.recv(""" - + """) # Give event queue time to process. @@ -136,15 +141,23 @@ class TestStreamPresence(SleekTest): self.xmpp.auto_subscribe = True self.recv(""" - + """) self.send(""" - + """) self.send(""" - + + """) + + self.send(""" + """) expected = set(('presence_subscribe', 'changed_subscription')) @@ -170,14 +183,17 @@ class TestStreamPresence(SleekTest): presence_subscribe) # With this setting we should reject all subscriptions. - self.xmpp.auto_authorize = False + self.xmpp.rosters['tester@localhost'].auto_authorize = False self.recv(""" - + """) self.send(""" - + """) expected = set(('presence_subscribe', 'changed_subscription')) diff --git a/tests/test_stream_roster.py b/tests/test_stream_roster.py index 165a8bc..8ffd86a 100644 --- a/tests/test_stream_roster.py +++ b/tests/test_stream_roster.py @@ -13,8 +13,7 @@ class TestStreamRoster(SleekTest): def testGetRoster(self): """Test handling roster requests.""" - self.stream_start(mode='client') - self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.") + self.stream_start(mode='client', jid='tester@localhost') # Since get_roster blocks, we need to run it in a thread. t = threading.Thread(name='get_roster', target=self.xmpp.get_roster) @@ -26,11 +25,12 @@ class TestStreamRoster(SleekTest): """) self.recv(""" - + + subscription="from" + ask="subscribe"> Friends Examples @@ -41,21 +41,20 @@ class TestStreamRoster(SleekTest): # Wait for get_roster to return. t.join() - roster = {'user@localhost': {'name': 'User', - 'subscription': 'both', - 'groups': ['Friends', 'Examples'], - 'presence': {}, - 'in_roster': True}} - self.failUnless(self.xmpp.roster == roster, - "Unexpected roster values: %s" % self.xmpp.roster) + print self.xmpp.rosters['tester@localhost']['user@localhost']._state + self.check_roster('tester@localhost', 'user@localhost', + name='User', + subscription='from', + afrom=True, + pending_out=True, + groups=['Friends', 'Examples']) def testRosterSet(self): """Test handling pushed roster updates.""" - self.stream_start(mode='client') - self.failUnless(self.xmpp.roster == {}, "Initial roster not empty.") + self.stream_start(mode='client', jid='tester@localhost') self.recv(""" - + """) - roster = {'user@localhost': {'name': 'User', - 'subscription': 'both', - 'groups': ['Friends', 'Examples'], - 'presence': {}, - 'in_roster': True}} - self.failUnless(self.xmpp.roster == roster, - "Unexpected roster values: %s" % self.xmpp.roster) - - + self.check_roster('tester@localhost', 'user@localhost', + name='User', + subscription='both', + groups=['Friends', 'Examples']) suite = unittest.TestLoader().loadTestsFromTestCase(TestStreamRoster) From 69d430dd75f526984fd6ea6f9db294731698712c Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 27 Oct 2010 08:09:50 -0400 Subject: [PATCH 16/20] Cleaned up names. --- sleekxmpp/basexmpp.py | 27 +++++++++++++-------------- sleekxmpp/clientxmpp.py | 2 +- sleekxmpp/roster.py | 35 +++++++++++++++++------------------ sleekxmpp/test/sleektest.py | 2 +- tests/test_stanza_presence.py | 3 ++- tests/test_stream_presence.py | 2 +- tests/test_stream_roster.py | 2 +- 7 files changed, 36 insertions(+), 37 deletions(-) diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py index fb718fb..46c8985 100644 --- a/sleekxmpp/basexmpp.py +++ b/sleekxmpp/basexmpp.py @@ -15,7 +15,7 @@ import logging import sleekxmpp from sleekxmpp import plugins -from sleekxmpp.roster import MultiRoster +import sleekxmpp.roster as roster from sleekxmpp.stanza import Message, Presence, Iq, Error from sleekxmpp.stanza.roster import Roster from sleekxmpp.stanza.nick import Nick @@ -111,9 +111,8 @@ class BaseXMPP(XMLStream): self.boundjid = JID(jid) self.plugin = {} - self.rosters = MultiRoster(self) - self.rosters.add(self.boundjid.bare) - self.roster = {} + self.roster = roster.Roster(self) + self.roster.add(self.boundjid.bare) self.is_component = False self.auto_authorize = True @@ -537,14 +536,14 @@ class BaseXMPP(XMLStream): self.event('message', msg) def _handle_available(self, presence): - self.rosters[presence['to'].bare][presence['from'].bare].handle_available(presence) + self.roster[presence['to'].bare][presence['from'].bare].handle_available(presence) def _handle_unavailable(self, presence): - self.rosters[presence['to'].bare][presence['from'].bare].handle_unavailable(presence) + self.roster[presence['to'].bare][presence['from'].bare].handle_unavailable(presence) def _handle_new_subscription(self, stanza): - roster = self.rosters[stanza['to'].bare] - item = self.rosters[stanza['to'].bare][stanza['from'].bare] + roster = self.roster[stanza['to'].bare] + item = self.roster[stanza['to'].bare][stanza['from'].bare] if item['whitelisted']: item.authorize() elif roster.auto_authorize: @@ -555,22 +554,22 @@ class BaseXMPP(XMLStream): item.unauthorize() def _handle_removed_subscription(self, presence): - self.rosters[presence['to'].bare][presence['from'].bare].unauthorize() + self.roster[presence['to'].bare][presence['from'].bare].unauthorize() def _handle_subscribe(self, stanza): - self.rosters[stanza['to'].bare][stanza['from'].bare].handle_subscribe(stanza) + self.roster[stanza['to'].bare][stanza['from'].bare].handle_subscribe(stanza) def _handle_subscribed(self, stanza): - self.rosters[stanza['to'].bare][stanza['from'].bare].handle_subscribed(stanza) + self.roster[stanza['to'].bare][stanza['from'].bare].handle_subscribed(stanza) def _handle_unsubscribe(self, stanza): - self.rosters[stanza['to'].bare][stanza['from'].bare].handle_unsubscribe(stanza) + self.roster[stanza['to'].bare][stanza['from'].bare].handle_unsubscribe(stanza) def _handle_unsubscribed(self, stanza): - self.rosters[stanza['to'].bare][stanza['from'].bare].handle_unsubscribed(stanza) + self.roster[stanza['to'].bare][stanza['from'].bare].handle_unsubscribed(stanza) def _handle_probe(self, stanza): - self.rosteritems[stanza['to'].bare][stanza['from'].bare].handle_probe(stanza) + self.roster[stanza['to'].bare][stanza['from'].bare].handle_probe(stanza) def _handle_presence(self, presence): """ diff --git a/sleekxmpp/clientxmpp.py b/sleekxmpp/clientxmpp.py index 8b8f05f..dbf8764 100644 --- a/sleekxmpp/clientxmpp.py +++ b/sleekxmpp/clientxmpp.py @@ -421,7 +421,7 @@ class ClientXMPP(BaseXMPP): if iq['type'] == 'set' or (iq['type'] == 'result' and request): for jid in iq['roster']['items']: item = iq['roster']['items'][jid] - roster = self.rosters[iq['to'].bare] + roster = self.roster[iq['to'].bare] roster[jid]['name'] = item['name'] roster[jid]['groups'] = item['groups'] roster[jid]['from'] = item['subscription'] in ['from', 'both'] diff --git a/sleekxmpp/roster.py b/sleekxmpp/roster.py index 32fd5c0..de96296 100644 --- a/sleekxmpp/roster.py +++ b/sleekxmpp/roster.py @@ -1,11 +1,11 @@ import logging -class MultiRoster(object): +class Roster(object): - def __init__(self, xmpp, datastore=None): + def __init__(self, xmpp, db=None): self.xmpp = xmpp - self.datastore = datastore + self.db = db self._rosters = {} def __getitem__(self, key): @@ -21,14 +21,14 @@ class MultiRoster(object): def add(self, node): if node not in self._rosters: - self._rosters[node] = Roster(self.xmpp, node, self.datastore) + self._rosters[node] = RosterNode(self.xmpp, node, self.db) -class Roster(object): +class RosterNode(object): - def __init__(self, xmpp, jid, datastore=None): + def __init__(self, xmpp, jid, db=None): self.xmpp = xmpp self.jid = jid - self.datastore = datastore + self.db = db self.auto_authorize = True self.auto_subscribe = True self._jids = {} @@ -56,7 +56,7 @@ class Roster(object): 'whitelisted': whitelisted, 'subscription': 'none'} self._jids[jid] = RosterItem(self.xmpp, jid, self.jid, - state=state, datastore=self.datastore) + state=state, db=self.db) if save: self._jids[jid].save() @@ -99,14 +99,13 @@ class Roster(object): class RosterItem(object): def __init__(self, xmpp, jid, owner=None, - state=None, datastore=None): + state=None, db=None): self.xmpp = xmpp self.jid = jid self.owner = owner or self.xmpp.jid self.last_status = None self.resources = {} - self.datastore = datastore - + self.db = db self._state = state or { 'from': False, 'to': False, @@ -116,13 +115,13 @@ class RosterItem(object): 'subscription': 'none', 'name': '', 'groups': []} - self._datastore_state = {} + self._db_state = {} self.load() def load(self): - if self.datastore: - item = self.datastore.load(self.owner, self.jid, - self._datastore_state) + if self.db: + item = self.db.load(self.owner, self.jid, + self._db_state) if item: self['name'] = item['name'] self['groups'] = item['groups'] @@ -136,9 +135,9 @@ class RosterItem(object): return None def save(self): - if self.datastore: - self.datastore.save(self.owner, self.jid, - self._state, self._datastore_state) + if self.db: + self.db.save(self.owner, self.jid, + self._state, self._db_state) def __getitem__(self, key): if key in self._state: diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index c481d73..0b6664b 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -140,7 +140,7 @@ class SleekTest(unittest.TestCase): def check_roster(self, owner, jid, name=None, subscription=None, afrom=None, ato=None, pending_out=None, pending_in=None, groups=None): - roster = self.xmpp.rosters[owner][jid] + roster = self.xmpp.roster[owner][jid] print roster._state if name is not None: self.assertEqual(roster['name'], name, diff --git a/tests/test_stanza_presence.py b/tests/test_stanza_presence.py index 8d043d5..b8600b6 100644 --- a/tests/test_stanza_presence.py +++ b/tests/test_stanza_presence.py @@ -49,7 +49,8 @@ class TestPresenceStanzas(SleekTest): self.failUnless(happened == [], "changed_status event triggered for extra unavailable presence") - self.failUnless(c.roster == {}, + roster = c.roster['crap@wherever'] + self.failUnless(roster['bill@chadmore.com'].resources == {}, "Roster updated for superfulous unavailable presence") def testNickPlugin(self): diff --git a/tests/test_stream_presence.py b/tests/test_stream_presence.py index 8ca1c0e..3e0933d 100644 --- a/tests/test_stream_presence.py +++ b/tests/test_stream_presence.py @@ -183,7 +183,7 @@ class TestStreamPresence(SleekTest): presence_subscribe) # With this setting we should reject all subscriptions. - self.xmpp.rosters['tester@localhost'].auto_authorize = False + self.xmpp.roster['tester@localhost'].auto_authorize = False self.recv(""" Date: Wed, 27 Oct 2010 09:27:00 -0400 Subject: [PATCH 17/20] Added docs for main Roster class. --- sleekxmpp/roster.py | 61 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/roster.py b/sleekxmpp/roster.py index de96296..8b6af7d 100644 --- a/sleekxmpp/roster.py +++ b/sleekxmpp/roster.py @@ -1,28 +1,82 @@ +""" + SleekXMPP: The Sleek XMPP Library + Copyright (C) 2010 Nathanael C. Fritz + This file is part of SleekXMPP. + + See the file LICENSE for copying permission. +""" + import logging class Roster(object): + """ + SleekXMPP's roster manager. + + The roster is divided into "nodes", where each node is responsible + for a single JID. While the distinction is not strictly necessary + for client connections, it is a necessity for components that use + multiple JIDs. + + Rosters may be stored and persisted in an external datastore. An + interface object to the datastore that loads and saves roster items may + be provided. See the documentation for the RosterItem class for the + methods that the datastore interface object must provide. + + Attributes: + xmpp -- The main SleekXMPP instance. + db -- Optional interface object to an external datastore. + + Methods: + add -- Create a new roster node for a JID. + """ + def __init__(self, xmpp, db=None): + """ + Create a new roster. + + Arguments: + xmpp -- The main SleekXMPP instance. + db -- An interface object to a datastore. + """ self.xmpp = xmpp self.db = db self._rosters = {} def __getitem__(self, key): + """ + Return the roster node for a JID. + + A new roster node will be created if one + does not already exist. + + Arguments: + key -- Return the roster for this JID. + """ if key not in self._rosters: - self.add(key) + self.add(key, self.db) return self._rosters[key] def keys(self): + """Return the JIDs managed by the roster.""" return self._rosters.keys() def __iter__(self): + """Iterate over the roster nodes.""" return self._rosters.__iter__() def add(self, node): + """ + Add a new roster node for the given JID. + + Arguments: + node -- The JID for the new roster node. + """ if node not in self._rosters: self._rosters[node] = RosterNode(self.xmpp, node, self.db) + class RosterNode(object): def __init__(self, xmpp, jid, db=None): @@ -169,7 +223,10 @@ class RosterItem(object): return 'none' def remove(self): - "Remove the jids subscription, inform it if it is subscribed, and unwhitelist it" + """ + Remove the jids subscription, inform it if it is + subscribed, and unwhitelist it. + """ if self['to']: p = self.xmpp.Presence() p['to'] = self.jid From 4260a754e580f26f3d64d23c53a14c358560241b Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 27 Oct 2010 10:51:58 -0400 Subject: [PATCH 18/20] Added more docs. --- sleekxmpp/roster.py | 108 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/sleekxmpp/roster.py b/sleekxmpp/roster.py index 8b6af7d..cced86b 100644 --- a/sleekxmpp/roster.py +++ b/sleekxmpp/roster.py @@ -38,7 +38,7 @@ class Roster(object): Arguments: xmpp -- The main SleekXMPP instance. - db -- An interface object to a datastore. + db -- Optional interface object to a datastore. """ self.xmpp = xmpp self.db = db @@ -79,7 +79,41 @@ class Roster(object): class RosterNode(object): + """ + + Attributes: + xmpp -- The main SleekXMPP instance. + jid -- The JID that owns the roster node. + db -- Optional interface to an external datastore. + auto_authorize -- Determines how authorizations are handled: + True -- Accept all subscriptions. + False -- Reject all subscriptions. + None -- Subscriptions must be + manually authorized. + Defaults to True. + auto_subscribe -- Determines if bi-directional subscriptions + are created after automatically authrorizing + a subscription request. + Defaults to True + + Methods: + add -- Add a JID to the roster. + update -- Update a JID's subscription information. + subscribe -- Subscribe to a JID. + unsubscribe -- Unsubscribe from a JID. + remove -- Remove a JID from the roster. + presence -- Return presence information for a JID's resources. + """ + def __init__(self, xmpp, jid, db=None): + """ + Create a roster node for a JID. + + Arguments: + xmpp -- The main SleekXMPP instance. + jid -- The JID that owns the roster. + db -- Optional interface to an external datastore. + """ self.xmpp = xmpp self.jid = jid self.db = db @@ -88,19 +122,52 @@ class RosterNode(object): self._jids = {} def __getitem__(self, key): + """ + Return the roster item for a subscribed JID. + + A new item entry will be created if one does not already exist. + """ if key not in self._jids: self.add(key, save=True) return self._jids[key] def keys(self): + """Return a list of all subscribed JIDs.""" return self._jids.keys() def __iter__(self): + """Iterate over the roster items.""" return self._jids.__iter__() def add(self, jid, name='', groups=None, afrom=False, ato=False, pending_in=False, pending_out=False, whitelisted=False, save=False): + """ + Add a new roster item entry. + + Arguments: + jid -- The JID for the roster item. + name -- An alias for the JID. + groups -- A list of group names. + afrom -- Indicates if the JID has a subscription state + of 'from'. Defaults to False. + ato -- Indicates if the JID has a subscription state + of 'to'. Defaults to False. + pending_in -- Indicates if the JID has sent a subscription + request to this connection's JID. + Defaults to False. + pending_out -- Indicates if a subscription request has been sent + to this JID. + Defaults to False. + whitelisted -- Indicates if a subscription request from this JID + should be automatically authorized. + Defaults to False. + save -- Indicates if the item should be persisted + immediately to an external datastore, + if one is used. + Defaults to False. + """ + state = {'name': name, 'groups': groups or [], 'from': afrom, @@ -115,17 +182,45 @@ class RosterNode(object): self._jids[jid].save() def subscribe(self, jid): + """ + Subscribe to the given JID. + + Arguments: + jid -- The JID to subscribe to. + """ self._jids[jid].subscribe() def unsubscribe(self, jid): + """ + Unsubscribe from the given JID. + + Arguments: + jid -- The JID to unsubscribe from. + """ self._jids[jid].unsubscribe() def remove(self, jid): + """ + Remove a JID from the roster. + + Arguments: + jid -- The JID to remove. + """ self._jids[jid].remove() if not self.xmpp.is_component: self.update(jid, subscription='remove') def update(self, jid, name=None, subscription=None, groups=[]): + """ + Update a JID's subscription information. + + Arguments: + jid -- The JID to update. + name -- Optional alias for the JID. + subscription -- The subscription state. May be one of: 'to', + 'from', 'both', 'none', or 'remove'. + groups -- A list of group names. + """ self._jids[jid]['name'] = name self._jids[jid]['groups'] = group self._jids[jid].save() @@ -140,6 +235,17 @@ class RosterNode(object): return response and response['type'] == 'result' def presence(self, jid, resource=None): + """ + Retrieve the presence information of a JID. + + May return either all online resources' status, or + a single resource's status. + + Arguments: + jid -- The JID to lookup. + resource -- Optional resource for returning + only the status of a single connection. + """ if resource is None: return self._jids[jid].resources From 20112f8e167439b9210834675fe8529cdcf6ce8a Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 27 Oct 2010 13:36:23 -0400 Subject: [PATCH 19/20] More docs! --- sleekxmpp/roster.py | 108 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 106 insertions(+), 2 deletions(-) diff --git a/sleekxmpp/roster.py b/sleekxmpp/roster.py index cced86b..77d2131 100644 --- a/sleekxmpp/roster.py +++ b/sleekxmpp/roster.py @@ -25,8 +25,12 @@ class Roster(object): methods that the datastore interface object must provide. Attributes: - xmpp -- The main SleekXMPP instance. - db -- Optional interface object to an external datastore. + xmpp -- The main SleekXMPP instance. + db -- Optional interface object to an external datastore. + auto_authorize -- Default auto_authorize value for new roster nodes. + Defaults to True. + auto_subscribe -- Default auto_subscribe value for new roster nodes. + Defaults to True. Methods: add -- Create a new roster node for a JID. @@ -42,6 +46,8 @@ class Roster(object): """ self.xmpp = xmpp self.db = db + self.auto_authorize = True + self.auto_subscribe = True self._rosters = {} def __getitem__(self, key): @@ -56,6 +62,8 @@ class Roster(object): """ if key not in self._rosters: self.add(key, self.db) + self._rosters[key].auto_authorize = self.auto_authorize + self._rosters[key].auto_subscribe = self.auto_subscribe return self._rosters[key] def keys(self): @@ -80,6 +88,7 @@ class Roster(object): class RosterNode(object): """ + A roster node is a roster for a single JID. Attributes: xmpp -- The main SleekXMPP instance. @@ -258,6 +267,101 @@ class RosterNode(object): class RosterItem(object): + """ + A RosterItem is a single entry in a roster node, and tracks + the subscription state and user annotations of a single JID. + + Roster items may use an external datastore to persist roster data + across sessions. Client applications will not need to use this + functionality, but is intended for components that do not have their + roster persisted automatically by the XMPP server. + + Roster items provide many methods for handling incoming presence + stanzas that ensure that response stanzas are sent according to + RFC 3921. + + The external datastore is accessed through a provided interface + object which is stored in self.db. The interface object MUST + provide two methods: load and save, both of which are responsible + for working with a single roster item. A private dictionary, + self._db_state, is used to store any metadata needed by the + interface, such as the row ID of a roster item, etc. + + Interface for self.db.load: + load(owner_jid, jid, db_state): + owner_jid -- The JID that owns the roster. + jid -- The JID of the roster item. + db_state -- A dictionary containing any data saved + by the interface object after a save() + call. Will typically have the equivalent + of a 'row_id' value. + + Interface for self.db.save: + save(owner_jid, jid, item_state, db_state): + owner_jid -- The JID that owns the roster. + jid -- The JID of the roster item. + item_state -- A dictionary containing the fields: + 'from', 'to', 'pending_in', 'pending_out', + 'whitelisted', 'subscription', 'name', + and 'groups'. + db_state -- A dictionary provided for persisting + datastore specific information. Typically, + a value equivalent to 'row_id' will be + stored here. + + State Fields: + from -- Indicates if a subscription of type 'from' + has been authorized. + to -- Indicates if a subscription of type 'to' has + been authorized. + pending_in -- Indicates if a subscription request has been + received from this JID and it has not been + authorized yet. + pending_out -- Indicates if a subscription request has been sent + to this JID and it has not been accepted yet. + subscription -- Returns one of: 'to', 'from', 'both', or 'none' + based on the states of from, to, pending_in, + and pending_out. Assignment to this value does + not affect the states of the other values. + whitelisted -- Indicates if a subscription request from this + JID should be automatically accepted. + name -- A user supplied alias for the JID. + groups -- A list of group names for the JID. + + Attributes: + xmpp -- The main SleekXMPP instance. + owner -- The JID that owns the roster. + jid -- The JID for the roster item. + db -- Optional datastore interface object. + last_status -- The last presence sent to this JID. + resources -- A dictionary of online resources for this JID. + Will contain the fields 'show', 'status', + and 'priority'. + + Methods: + load -- Retrieve the roster item from an + external datastore, if one was provided. + save -- Save the roster item to an external + datastore, if one was provided. + remove -- Remove a subscription to the JID and revoke + its whitelisted status. + subscribe -- Subscribe to the JID. + authorize -- Accept a subscription from the JID. + unauthorize -- Deny a subscription from the JID. + unsubscribe -- Unsubscribe from the JID. + send_presence -- Send a directed presence to the JID. + send_last_presence -- Resend the last sent presence. + handle_available -- Update the JID's resource information. + handle_unavailable -- Update the JID's resource information. + handle_subscribe -- Handle a subscription request. + handle_subscribed -- Handle a notice that a subscription request + was authorized by the JID. + handle_unsubscribe -- Handle an unsubscribe request. + handle_unsubscribed -- Handle a notice that a subscription was + removed by the JID. + handle_probe -- Handle a presence probe query. + """ + def __init__(self, xmpp, jid, owner=None, state=None, db=None): self.xmpp = xmpp From 5424ede413393db5b835686e8544a9b703fb113b Mon Sep 17 00:00:00 2001 From: Lance Stout Date: Wed, 27 Oct 2010 15:02:21 -0400 Subject: [PATCH 20/20] More cleanup. --- sleekxmpp/roster.py | 44 +++++++++++++++++++++++++++++++++---- sleekxmpp/test/sleektest.py | 1 - tests/test_stream_roster.py | 1 - 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/sleekxmpp/roster.py b/sleekxmpp/roster.py index 77d2131..da48533 100644 --- a/sleekxmpp/roster.py +++ b/sleekxmpp/roster.py @@ -364,9 +364,20 @@ class RosterItem(object): def __init__(self, xmpp, jid, owner=None, state=None, db=None): + """ + Create a new roster item. + + Arguments: + xmpp -- The main SleekXMPP instance. + jid -- The item's JID. + owner -- The roster owner's JID. Defaults + so self.xmpp.boundjid.bare. + state -- A dictionary of initial state values. + db -- An optional interface to an external datastore. + """ self.xmpp = xmpp self.jid = jid - self.owner = owner or self.xmpp.jid + self.owner = owner or self.xmpp.boundjid.bare self.last_status = None self.resources = {} self.db = db @@ -383,6 +394,10 @@ class RosterItem(object): self.load() def load(self): + """ + Load the item's state information from an external datastore, + if one has been provided. + """ if self.db: item = self.db.load(self.owner, self.jid, self._db_state) @@ -399,11 +414,16 @@ class RosterItem(object): return None def save(self): + """ + Save the item's state information to an external datastore, + if one has been provided. + """ if self.db: self.db.save(self.owner, self.jid, self._state, self._db_state) def __getitem__(self, key): + """Return a state field's value.""" if key in self._state: if key == 'subscription': return self._subscription() @@ -412,7 +432,16 @@ class RosterItem(object): raise KeyError def __setitem__(self, key, value): - print "%s: %s" % (key, value) + """ + Set the value of a state field. + + For boolean states, the values True, 'true', '1', 'on', + and 'yes' are accepted as True; all others are False. + + Arguments: + key -- The state field to modify. + value -- The new value of the state field. + """ if key in self._state: if key in ['name', 'subscription', 'groups']: self._state[key] = value @@ -423,6 +452,7 @@ class RosterItem(object): raise KeyError def _subscription(self): + """Return the proper subscription type based on current state.""" if self['to'] and self['from']: return 'both' elif self['from']: @@ -434,8 +464,8 @@ class RosterItem(object): def remove(self): """ - Remove the jids subscription, inform it if it is - subscribed, and unwhitelist it. + Remove a JID's whitelisted status and unsubscribe if a + subscription exists. """ if self['to']: p = self.xmpp.Presence() @@ -449,6 +479,7 @@ class RosterItem(object): self.save() def subscribe(self): + """Send a subscription request to the JID.""" p = self.xmpp.Presence() p['to'] = self.jid p['type'] = 'subscribe' @@ -459,6 +490,7 @@ class RosterItem(object): p.send() def authorize(self): + """Authorize a received subscription request from the JID.""" self['from'] = True self['pending_in'] = False self.save() @@ -466,6 +498,7 @@ class RosterItem(object): self.send_last_presence() def unauthorize(self): + """Deny a received subscription request from the JID.""" self['from'] = False self['pending_in'] = False self.save() @@ -478,6 +511,7 @@ class RosterItem(object): p.send() def _subscribed(self): + """Handle acknowledging a subscription.""" p = self.xmpp.Presence() p['to'] = self.jid p['type'] = 'subscribed' @@ -486,6 +520,7 @@ class RosterItem(object): p.send() def unsubscribe(self): + """Unsubscribe from the JID.""" p = self.xmpp.Presence() p['to'] = self.jid p['type'] = 'unsubscribe' @@ -495,6 +530,7 @@ class RosterItem(object): p.send() def _unsubscribed(self): + """Handle acknowledging an unsubscribe request.""" p = self.xmpp.Presence() p['to'] = self.jid p['type'] = 'unsubscribed' diff --git a/sleekxmpp/test/sleektest.py b/sleekxmpp/test/sleektest.py index 0b6664b..e49b5fe 100644 --- a/sleekxmpp/test/sleektest.py +++ b/sleekxmpp/test/sleektest.py @@ -141,7 +141,6 @@ class SleekTest(unittest.TestCase): afrom=None, ato=None, pending_out=None, pending_in=None, groups=None): roster = self.xmpp.roster[owner][jid] - print roster._state if name is not None: self.assertEqual(roster['name'], name, "Incorrect name value: %s" % roster['name']) diff --git a/tests/test_stream_roster.py b/tests/test_stream_roster.py index 7edaecd..dffd671 100644 --- a/tests/test_stream_roster.py +++ b/tests/test_stream_roster.py @@ -41,7 +41,6 @@ class TestStreamRoster(SleekTest): # Wait for get_roster to return. t.join() - print self.xmpp.roster['tester@localhost']['user@localhost']._state self.check_roster('tester@localhost', 'user@localhost', name='User', subscription='from',