diff options
Diffstat (limited to 'slixmpp')
-rw-r--r-- | slixmpp/plugins/xep_0009/stanza/RPC.py | 2 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0045.py | 50 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0196/stanza.py | 3 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0198/stream_management.py | 46 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0323/stanza/sensordata.py | 2 | ||||
-rw-r--r-- | slixmpp/plugins/xep_0363/http_upload.py | 8 | ||||
-rw-r--r-- | slixmpp/thirdparty/mini_dateutil.py | 2 | ||||
-rw-r--r-- | slixmpp/version.py | 4 | ||||
-rw-r--r-- | slixmpp/xmlstream/stanzabase.py | 15 | ||||
-rw-r--r-- | slixmpp/xmlstream/xmlstream.py | 47 |
10 files changed, 117 insertions, 62 deletions
diff --git a/slixmpp/plugins/xep_0009/stanza/RPC.py b/slixmpp/plugins/xep_0009/stanza/RPC.py index f8cec481..542c839c 100644 --- a/slixmpp/plugins/xep_0009/stanza/RPC.py +++ b/slixmpp/plugins/xep_0009/stanza/RPC.py @@ -7,7 +7,7 @@ """ from slixmpp.xmlstream.stanzabase import ElementBase -from xml.etree import cElementTree as ET +from xml.etree import ElementTree as ET class RPCQuery(ElementBase): diff --git a/slixmpp/plugins/xep_0045.py b/slixmpp/plugins/xep_0045.py index 30769b5c..dfbb3b58 100644 --- a/slixmpp/plugins/xep_0045.py +++ b/slixmpp/plugins/xep_0045.py @@ -29,61 +29,61 @@ class MUCPresence(ElementBase): affiliations = {'', } roles = {'', } - def get_xml_item(self): + def get_item_attr(self, attr, default): + item = self.xml.find('{http://jabber.org/protocol/muc#user}item') + if item is None: + return default + return item.get(attr) + + def set_item_attr(self, attr, value): item = self.xml.find('{http://jabber.org/protocol/muc#user}item') if item is None: item = ET.Element('{http://jabber.org/protocol/muc#user}item') self.xml.append(item) + item.attrib[attr] = value return item + def del_item_attr(self, attr): + item = self.xml.find('{http://jabber.org/protocol/muc#user}item') + if item is not None and attr in item.attrib: + del item.attrib[attr] + def get_affiliation(self): - #TODO if no affilation, set it to the default and return default - item = self.get_xml_item() - return item.get('affiliation', '') + return self.get_item_attr('affiliation', '') def set_affiliation(self, value): - item = self.get_xml_item() - #TODO check for valid affiliation - item.attrib['affiliation'] = value + self.set_item_attr('affiliation', value) return self def del_affiliation(self): - item = self.get_xml_item() - #TODO set default affiliation - if 'affiliation' in item.attrib: del item.attrib['affiliation'] + # TODO: set default affiliation + self.del_item_attr('affiliation') return self def get_jid(self): - item = self.get_xml_item() - return JID(item.get('jid', '')) + return JID(self.get_item_attr('jid', '')) def set_jid(self, value): - item = self.get_xml_item() if not isinstance(value, str): value = str(value) - item.attrib['jid'] = value + self.set_item_attr('jid', value) return self def del_jid(self): - item = self.get_xml_item() - if 'jid' in item.attrib: del item.attrib['jid'] + self.del_item_attr('jid') return self def get_role(self): - item = self.get_xml_item() - #TODO get default role, set default role if none - return item.get('role', '') + return self.get_item_attr('role', '') def set_role(self, value): - item = self.get_xml_item() - #TODO check for valid role - item.attrib['role'] = value + # TODO: check for valid role + self.set_item_attr('role', value) return self def del_role(self): - item = self.get_xml_item() - #TODO set default role - if 'role' in item.attrib: del item.attrib['role'] + # TODO: set default role + self.del_item_attr('role') return self def get_nick(self): diff --git a/slixmpp/plugins/xep_0196/stanza.py b/slixmpp/plugins/xep_0196/stanza.py index 79f5621e..756208b2 100644 --- a/slixmpp/plugins/xep_0196/stanza.py +++ b/slixmpp/plugins/xep_0196/stanza.py @@ -11,10 +11,9 @@ from slixmpp.xmlstream import ElementBase, ET class UserGaming(ElementBase): - name = 'gaming' + name = 'game' namespace = 'urn:xmpp:gaming:0' plugin_attrib = 'gaming' interfaces = {'character_name', 'character_profile', 'name', 'level', 'server_address', 'server_name', 'uri'} sub_interfaces = interfaces - diff --git a/slixmpp/plugins/xep_0198/stream_management.py b/slixmpp/plugins/xep_0198/stream_management.py index 759e82e1..0200646a 100644 --- a/slixmpp/plugins/xep_0198/stream_management.py +++ b/slixmpp/plugins/xep_0198/stream_management.py @@ -71,7 +71,8 @@ class XEP_0198(BasePlugin): self.window_counter = self.window - self.enabled = False + self.enabled_in = False + self.enabled_out = False self.unacked_queue = collections.deque() register_stanza_plugin(StreamFeatures, stanza.StreamManagement) @@ -82,10 +83,6 @@ class XEP_0198(BasePlugin): self.xmpp.register_stanza(stanza.Ack) self.xmpp.register_stanza(stanza.RequestAck) - # Only end the session when a </stream> element is sent, - # not just because the connection has died. - self.xmpp.end_session_on_disconnect = False - # Register the feature twice because it may be ordered two # different ways: enabling after binding and resumption # before binding. @@ -131,6 +128,7 @@ class XEP_0198(BasePlugin): self.xmpp.add_filter('in', self._handle_incoming) self.xmpp.add_filter('out_sync', self._handle_outgoing) + self.xmpp.add_event_handler('disconnected', self.disconnected) self.xmpp.add_event_handler('session_end', self.session_end) def plugin_end(self): @@ -139,6 +137,7 @@ class XEP_0198(BasePlugin): self.xmpp.unregister_feature('sm', self.order) self.xmpp.unregister_feature('sm', self.resume_order) + self.xmpp.del_event_handler('disconnected', self.disconnected) self.xmpp.del_event_handler('session_end', self.session_end) self.xmpp.del_filter('in', self._handle_incoming) self.xmpp.del_filter('out_sync', self._handle_outgoing) @@ -154,9 +153,19 @@ class XEP_0198(BasePlugin): self.xmpp.remove_stanza(stanza.Ack) self.xmpp.remove_stanza(stanza.RequestAck) + def disconnected(self, event): + """Reset enabled state until we can resume/reenable.""" + log.debug("disconnected, disabling SM") + self.xmpp.event('sm_disabled', event) + self.enabled_in = False + self.enabled_out = False + def session_end(self, event): """Reset stream management state.""" - self.enabled = False + log.debug("session_end, disabling SM") + self.xmpp.event('sm_disabled', event) + self.enabled_in = False + self.enabled_out = False self.unacked_queue.clear() self.sm_id = None self.handled = 0 @@ -171,6 +180,7 @@ class XEP_0198(BasePlugin): def request_ack(self, e=None): """Request an ack from the server.""" + log.debug("requesting ack") req = stanza.RequestAck(self.xmpp) self.xmpp.send_raw(str(req)) @@ -193,9 +203,7 @@ class XEP_0198(BasePlugin): enable = stanza.Enable(self.xmpp) enable['resume'] = self.allow_resume enable.send() - self.enabled = True - self.handled = 0 - self.unacked_queue.clear() + log.debug("enabling SM") waiter = Waiter('enabled_or_failed', MatchMany([ @@ -204,11 +212,11 @@ class XEP_0198(BasePlugin): self.xmpp.register_handler(waiter) result = await waiter.wait() elif self.sm_id and self.allow_resume and 'bind' not in self.xmpp.features: - self.enabled = True resume = stanza.Resume(self.xmpp) resume['h'] = self.handled resume['previd'] = self.sm_id resume.send() + log.debug("resuming SM") # Wait for a response before allowing stream feature processing # to continue. The actual result processing will be done in the @@ -231,7 +239,10 @@ class XEP_0198(BasePlugin): self.xmpp.features.add('stream_management') if stanza['id']: self.sm_id = stanza['id'] + self.enabled_in = True + self.handled = 0 self.xmpp.event('sm_enabled', stanza) + self.xmpp.end_session_on_disconnect = False def _handle_resumed(self, stanza): """Finish resuming a stream by resending unacked stanzas. @@ -239,10 +250,12 @@ class XEP_0198(BasePlugin): Raises a :term:`session_resumed` event. """ self.xmpp.features.add('stream_management') + self.enabled_in = True self._handle_ack(stanza) for id, stanza in self.unacked_queue: self.xmpp.send(stanza, use_filters=False) self.xmpp.event('session_resumed', stanza) + self.xmpp.end_session_on_disconnect = False def _handle_failed(self, stanza): """ @@ -252,7 +265,8 @@ class XEP_0198(BasePlugin): Raises an :term:`sm_failed` event. """ - self.enabled = False + self.enabled_in = False + self.enabled_out = False self.unacked_queue.clear() self.xmpp.event('sm_failed', stanza) @@ -289,7 +303,7 @@ class XEP_0198(BasePlugin): def _handle_incoming(self, stanza): """Increment the handled counter for each inbound stanza.""" - if not self.enabled: + if not self.enabled_in: return stanza if isinstance(stanza, (Message, Presence, Iq)): @@ -299,7 +313,13 @@ class XEP_0198(BasePlugin): def _handle_outgoing(self, stanza): """Store outgoing stanzas in a queue to be acked.""" - if not self.enabled: + from slixmpp.plugins.xep_0198 import stanza as st + if isinstance(stanza, (st.Enable, st.Resume)): + self.enabled_out = True + self.unacked_queue.clear() + log.debug("enabling outgoing SM: %s" % stanza) + + if not self.enabled_out: return stanza if isinstance(stanza, (Message, Presence, Iq)): diff --git a/slixmpp/plugins/xep_0323/stanza/sensordata.py b/slixmpp/plugins/xep_0323/stanza/sensordata.py index c0906cac..7ab1e3ba 100644 --- a/slixmpp/plugins/xep_0323/stanza/sensordata.py +++ b/slixmpp/plugins/xep_0323/stanza/sensordata.py @@ -516,7 +516,7 @@ class Field(ElementBase): :param value: string """ - pattern = re.compile("^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$") + pattern = re.compile(r"^\d+([|]\w+([.]\w+)*([|][^,]*)?)?(,\d+([|]\w+([.]\w+)*([|][^,]*)?)?)*$") if pattern.match(value) is not None: self.xml.stringIds = value else: diff --git a/slixmpp/plugins/xep_0363/http_upload.py b/slixmpp/plugins/xep_0363/http_upload.py index 266fc656..a833a9c9 100644 --- a/slixmpp/plugins/xep_0363/http_upload.py +++ b/slixmpp/plugins/xep_0363/http_upload.py @@ -28,7 +28,9 @@ class UploadServiceNotFound(FileUploadError): pass class FileTooBig(FileUploadError): - pass + def __str__(self): + return 'File size too large: {} (max: {} bytes)' \ + .format(self.args[0], self.args[1]) class HTTPError(FileUploadError): def __str__(self): @@ -116,7 +118,7 @@ class XEP_0363(BasePlugin): except (TypeError, ValueError): log.error('Invalid max size received from HTTP File Upload service') self.max_file_size = float('+inf') - break + break if input_file is None: input_file = open(filename, 'rb') @@ -126,7 +128,7 @@ class XEP_0363(BasePlugin): input_file.seek(0) if size > self.max_file_size: - raise FileTooBig() + raise FileTooBig(size, self.max_file_size) if content_type is None: content_type = guess_type(filename)[0] diff --git a/slixmpp/thirdparty/mini_dateutil.py b/slixmpp/thirdparty/mini_dateutil.py index e751a448..882a531f 100644 --- a/slixmpp/thirdparty/mini_dateutil.py +++ b/slixmpp/thirdparty/mini_dateutil.py @@ -160,7 +160,7 @@ except: return _fixed_offset_tzs[offsetmins] - _iso8601_parser = re.compile(""" + _iso8601_parser = re.compile(r""" ^ (?P<year> [0-9]{4})?(?P<ymdsep>-?)? (?P<month>[0-9]{2})?(?P=ymdsep)? diff --git a/slixmpp/version.py b/slixmpp/version.py index feb173e2..757b5473 100644 --- a/slixmpp/version.py +++ b/slixmpp/version.py @@ -9,5 +9,5 @@ # We don't want to have to import the entire library # just to get the version info for setup.py -__version__ = '1.4.2' -__version_info__ = (1, 4, 2) +__version__ = '1.5.2' +__version_info__ = (1, 5, 2) diff --git a/slixmpp/xmlstream/stanzabase.py b/slixmpp/xmlstream/stanzabase.py index 3e45f613..f45e4b96 100644 --- a/slixmpp/xmlstream/stanzabase.py +++ b/slixmpp/xmlstream/stanzabase.py @@ -17,7 +17,7 @@ from __future__ import with_statement, unicode_literals import copy import logging import weakref -from xml.etree import cElementTree as ET +from xml.etree import ElementTree as ET from slixmpp.xmlstream import JID from slixmpp.xmlstream.tostring import tostring @@ -203,7 +203,7 @@ class ElementBase(object): """ The core of Slixmpp's stanza XML manipulation and handling is provided - by ElementBase. ElementBase wraps XML cElementTree objects and enables + by ElementBase. ElementBase wraps XML ElementTree objects and enables access to the XML contents through dictionary syntax, similar in style to the Ruby XMPP library Blather's stanza implementation. @@ -387,7 +387,7 @@ class ElementBase(object): self._index = 0 #: The underlying XML object for the stanza. It is a standard - #: :class:`xml.etree.cElementTree` object. + #: :class:`xml.etree.ElementTree` object. self.xml = xml #: An ordered dictionary of plugin stanzas, mapped by their @@ -1031,14 +1031,19 @@ class ElementBase(object): if not lang: lang = default_lang + parent = self.xml for level, _ in enumerate(path): # Generate the paths to the target elements and their parent. element_path = "/".join(path[:len(path) - level]) parent_path = "/".join(path[:len(path) - level - 1]) elements = self.xml.findall(element_path) - parent = self.xml.find(parent_path) - + + if parent_path == '': + parent_path = None + if parent_path is not None: + parent = self.xml.find(parent_path) + if elements: if parent is None: parent = self.xml diff --git a/slixmpp/xmlstream/xmlstream.py b/slixmpp/xmlstream/xmlstream.py index dbf515ca..3aac8c8e 100644 --- a/slixmpp/xmlstream/xmlstream.py +++ b/slixmpp/xmlstream/xmlstream.py @@ -277,6 +277,7 @@ class XMLStream(asyncio.BaseProtocol): ) self.disconnect_reason = None self.cancel_connection_attempt() + self.connect_loop_wait = 0 if host and port: self.address = (host, int(port)) try: @@ -301,6 +302,10 @@ class XMLStream(asyncio.BaseProtocol): async def _connect_routine(self): self.event_when_connected = "connected" + if self.connect_loop_wait > 0: + self.event('reconnect_delay', self.connect_loop_wait) + await asyncio.sleep(self.connect_loop_wait, loop=self.loop) + record = await self.pick_dns_answer(self.default_domain) if record is not None: host, address, dns_port = record @@ -317,7 +322,6 @@ class XMLStream(asyncio.BaseProtocol): else: ssl_context = None - await asyncio.sleep(self.connect_loop_wait, loop=self.loop) if self._current_connection_attempt is None: return try: @@ -376,6 +380,7 @@ class XMLStream(asyncio.BaseProtocol): "ssl_object", default=self.transport.get_extra_info("socket") ) + self._current_connection_attempt = None self.init_parser() self.send_raw(self.stream_header) self.dns_answers = None @@ -434,6 +439,9 @@ class XMLStream(asyncio.BaseProtocol): self.send(error) self.disconnect() + def is_connecting(self): + return self._current_connection_attempt is not None + def is_connected(self): return self.transport is not None @@ -467,7 +475,7 @@ class XMLStream(asyncio.BaseProtocol): self._current_connection_attempt.cancel() self._current_connection_attempt = None - def disconnect(self, wait: float = 2.0, reason: Optional[str] = None) -> None: + def disconnect(self, wait: float = 2.0, reason: Optional[str] = None, ignore_send_queue: bool = False) -> None: """Close the XML stream and wait for an acknowldgement from the server for at most `wait` seconds. After the given number of seconds has passed without a response from the server, or when the server @@ -487,13 +495,31 @@ class XMLStream(asyncio.BaseProtocol): if wait == True: wait = 2.0 + if self.transport: + if self.waiting_queue.empty() or ignore_send_queue: + self.disconnect_reason = reason + self.cancel_connection_attempt() + if wait > 0.0: + self.send_raw(self.stream_footer) + self.schedule('Disconnect wait', wait, + self.abort, repeat=False) + else: + asyncio.ensure_future( + self._consume_send_queue_before_disconnecting(reason, wait), + loop=self.loop, + ) + else: + self.event("disconnected", reason) + + async def _consume_send_queue_before_disconnecting(self, reason: Optional[str], wait: float): + """Wait until the send queue is empty before disconnecting""" + await self.waiting_queue.join() self.disconnect_reason = reason self.cancel_connection_attempt() - if self.transport: - if wait > 0.0: - self.send_raw(self.stream_footer) - self.schedule('Disconnect wait', wait, - self.abort, repeat=False) + if wait > 0.0: + self.send_raw(self.stream_footer) + self.schedule('Disconnect wait', wait, + self.abort, repeat=False) def abort(self): """ @@ -506,14 +532,15 @@ class XMLStream(asyncio.BaseProtocol): self.event("killed") self.disconnected.set_result(True) self.disconnected = asyncio.Future() + self.event("disconnected", self.disconnect_reason) def reconnect(self, wait=2.0, reason="Reconnecting"): """Calls disconnect(), and once we are disconnected (after the timeout, or when the server acknowledgement is received), call connect() """ log.debug("reconnecting...") - self.disconnect(wait, reason) self.add_event_handler('disconnected', lambda event: self.connect(), disposable=True) + self.disconnect(wait, reason) def configure_socket(self): """Set timeout and other options for self.socket. @@ -888,7 +915,9 @@ class XMLStream(asyncio.BaseProtocol): Execute the callback and remove the handler for it. """ self._safe_cb_run(name, cb) - del self.scheduled_events[name] + # workaround for specific events which unschedule themselves + if name in self.scheduled_events: + del self.scheduled_events[name] def incoming_filter(self, xml): """Filter incoming XML objects before they are processed. |