diff options
41 files changed, 180 insertions, 140 deletions
diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..50ea54cb --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,13 @@ +################ Please use Gitlab instead of Github ################################### + +Hello, thank you for contributing to slixmpp! + +You’re about to open a pull request on github. However this github repository is not the official place for contributions on slixmpp. + +Please open your merge request on https://lab.louiz.org/poezio/slixmpp/ + +You should be able to log in there with your github credentials, clone the slixmpp repository in your namespace, push your existing pull request into a new branch, and then open a merge request with one click, within 3 minutes. + +This will help us review your contribution, avoid spreading things everywhere and it will even run the tests automatically with your changes. + +Thank you. diff --git a/.travis.yml b/.travis.yml index f503be34..be8e089c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,7 @@ language: python python: - - "3.4" - - "3.5" - - "3.6" - - "3.7-dev" + - "3.7" + - "3.8-dev" install: - "pip install ." script: testall.py @@ -1,5 +1,5 @@ Pre-requisites: -- Python 3.5+ +- Python 3.7+ - Cython 0.22 and libidn, optionally (making JID faster by compiling the stringprep module) - GnuPG, for testing @@ -1,7 +1,7 @@ Slixmpp ######### -Slixmpp is an MIT licensed XMPP library for Python 3.5+. It is a fork of +Slixmpp is an MIT licensed XMPP library for Python 3.7+. It is a fork of SleekXMPP. Slixmpp's goals is to only rewrite the core of the library (the low level diff --git a/docs/_static/haiku.css b/docs/_static/haiku.css index 3d8ee6a7..a76f55de 100644 --- a/docs/_static/haiku.css +++ b/docs/_static/haiku.css @@ -408,24 +408,3 @@ div.viewcode-block:target { margin: -1px -12px; padding: 0 12px; } - -#from_andyet { - -webkit-box-shadow: #CCC 0px 0px 3px; - background: rgba(255, 255, 255, 1); - bottom: 0px; - right: 17px; - padding: 3px 10px; - position: fixed; -} - -#from_andyet h2 { - background-image: url("images/from_&yet.png"); - background-repeat: no-repeat; - height: 29px; - line-height: 0; - text-indent: -9999em; - width: 79px; - margin-top: 0; - margin: 0px; - padding: 0px; -} diff --git a/docs/_templates/layout.html b/docs/_templates/layout.html index de6f7244..0a97cb70 100644 --- a/docs/_templates/layout.html +++ b/docs/_templates/layout.html @@ -65,6 +65,5 @@ <div class="bottomnav"> {{ nav() }} </div> - <a id="from_andyet" href="http://andyet.net"><h2>From &yet</h2></a> {% endblock %} diff --git a/docs/api/xmlstream/tostring.rst b/docs/api/xmlstream/tostring.rst index 68abbdb6..107e97b0 100644 --- a/docs/api/xmlstream/tostring.rst +++ b/docs/api/xmlstream/tostring.rst @@ -13,7 +13,7 @@ hides namespaces when able and does not introduce excessive namespace prefixes:: >>> from slixmpp.xmlstream.tostring import tostring - >>> from xml.etree import cElementTree as ET + >>> from xml.etree import ElementTree as ET >>> xml = ET.fromstring('<foo xmlns="bar"><baz /></foo>') >>> ET.tostring(xml) '<ns0:foo xmlns:ns0="bar"><ns0:baz /></foo>' diff --git a/docs/differences.rst b/docs/differences.rst index 8a86427b..7c781571 100644 --- a/docs/differences.rst +++ b/docs/differences.rst @@ -3,8 +3,9 @@ Differences from SleekXMPP ========================== -**Python 3.5+ only** - slixmpp will only work on python 3.5 and above. +**Python 3.7+ only** + slixmpp will work on python 3.7 and above. It may work with previous + versions but we provide no guarantees. **Stanza copies** The same stanza object is given through all the handlers; a handler that diff --git a/docs/index.rst b/docs/index.rst index 2f556d83..a18c77a7 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Slixmpp which goal is to use asyncio instead of threads to handle networking. See :ref:`differences`. -Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.5+, +Slixmpp is an :ref:`MIT licensed <license>` XMPP library for Python 3.7+, Slixmpp's design goals and philosphy are: diff --git a/examples/adhoc_provider.py b/examples/adhoc_provider.py index 2bab2f46..d2c3afd6 100755 --- a/examples/adhoc_provider.py +++ b/examples/adhoc_provider.py @@ -33,7 +33,7 @@ class CommandBot(slixmpp.ClientXMPP): # our roster. self.add_event_handler("session_start", self.start) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -47,7 +47,7 @@ class CommandBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() # We add the command after session_start has fired # to ensure that the correct full JID is used. diff --git a/examples/adhoc_user.py b/examples/adhoc_user.py index 8bdb675b..931ef71c 100755 --- a/examples/adhoc_user.py +++ b/examples/adhoc_user.py @@ -37,7 +37,7 @@ class CommandUserBot(slixmpp.ClientXMPP): self.add_event_handler("session_start", self.start) self.add_event_handler("message", self.message) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -51,7 +51,7 @@ class CommandUserBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() # We first create a session dictionary containing: # 'next' -- the handler to execute on a successful response @@ -176,4 +176,4 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() - xmpp.process() + xmpp.process(forever=False) diff --git a/examples/admin_commands.py b/examples/admin_commands.py index 72577f87..64c48913 100755 --- a/examples/admin_commands.py +++ b/examples/admin_commands.py @@ -30,7 +30,7 @@ class AdminCommands(slixmpp.ClientXMPP): self.add_event_handler("session_start", self.start) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -44,7 +44,7 @@ class AdminCommands(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() def command_success(iq, session): print('Command completed') diff --git a/examples/disco_browser.py b/examples/disco_browser.py index f0ece32d..02d51259 100755 --- a/examples/disco_browser.py +++ b/examples/disco_browser.py @@ -69,7 +69,7 @@ class Disco(slixmpp.ClientXMPP): event does not provide any additional data. """ - self.get_roster() + await self.get_roster() self.send_presence() try: diff --git a/examples/download_avatars.py b/examples/download_avatars.py index 02591e3e..37733b01 100755 --- a/examples/download_avatars.py +++ b/examples/download_avatars.py @@ -159,4 +159,4 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() - xmpp.process() + xmpp.process(forever=False) diff --git a/examples/echo_client.py b/examples/echo_client.py index 820ca014..2a3337a5 100755 --- a/examples/echo_client.py +++ b/examples/echo_client.py @@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP): # MUC messages and error messages. self.add_event_handler("message", self.message) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() def message(self, msg): """ diff --git a/examples/gtalk_custom_domain.py b/examples/gtalk_custom_domain.py index f055159b..c8d80e96 100755 --- a/examples/gtalk_custom_domain.py +++ b/examples/gtalk_custom_domain.py @@ -58,7 +58,7 @@ class GTalkBot(slixmpp.ClientXMPP): logging.error(err.message) self.disconnect() - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -72,7 +72,7 @@ class GTalkBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() def message(self, msg): """ diff --git a/examples/markup.py b/examples/markup.py index a72cbdb8..4c850ec8 100755 --- a/examples/markup.py +++ b/examples/markup.py @@ -39,7 +39,7 @@ class EchoBot(slixmpp.ClientXMPP): # MUC messages and error messages. self.add_event_handler("message", self.message) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -53,7 +53,7 @@ class EchoBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() def message(self, msg): """ diff --git a/examples/migrate_roster.py b/examples/migrate_roster.py index d599b10c..be457fb3 100755 --- a/examples/migrate_roster.py +++ b/examples/migrate_roster.py @@ -104,4 +104,4 @@ def on_session2(event): new_xmpp.add_event_handler('session_start', on_session2) new_xmpp.connect() -new_xmpp.process() +new_xmpp.process(forever=False) diff --git a/examples/muc.py b/examples/muc.py index 469e8f49..e3433b8f 100755 --- a/examples/muc.py +++ b/examples/muc.py @@ -52,7 +52,7 @@ class MUCBot(slixmpp.ClientXMPP): self.muc_online) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -65,7 +65,7 @@ class MUCBot(slixmpp.ClientXMPP): event does not provide any additional data. """ - self.get_roster() + await self.get_roster() self.send_presence() self.plugin['xep_0045'].join_muc(self.room, self.nick, diff --git a/examples/ping.py b/examples/ping.py index cb1bb968..7870715c 100755 --- a/examples/ping.py +++ b/examples/ping.py @@ -51,7 +51,7 @@ class PingTest(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() try: rtt = await self['xep_0199'].ping(self.pingjid, @@ -109,4 +109,4 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() - xmpp.process() + xmpp.process(forever=False) diff --git a/examples/proxy_echo_client.py b/examples/proxy_echo_client.py index b149de31..566f5957 100755 --- a/examples/proxy_echo_client.py +++ b/examples/proxy_echo_client.py @@ -38,7 +38,7 @@ class EchoBot(slixmpp.ClientXMPP): # MUC messages and error messages. self.add_event_handler("message", self.message) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -52,7 +52,7 @@ class EchoBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() def message(self, msg): """ diff --git a/examples/pubsub_client.py b/examples/pubsub_client.py index 480c7d89..e285dfc8 100755 --- a/examples/pubsub_client.py +++ b/examples/pubsub_client.py @@ -32,7 +32,7 @@ class PubsubClient(slixmpp.ClientXMPP): self.add_event_handler('session_start', self.start) async def start(self, event): - self.get_roster() + await self.get_roster() self.send_presence() try: diff --git a/examples/pubsub_events.py b/examples/pubsub_events.py index e2fdc9cf..2b3a1e65 100755 --- a/examples/pubsub_events.py +++ b/examples/pubsub_events.py @@ -38,8 +38,8 @@ class PubsubEvents(slixmpp.ClientXMPP): # self.add_event_handler('event_prefix_purge', handler) # self.add_event_handler('event_prefix_delete', handler) - def start(self, event): - self.get_roster() + async def start(self, event): + await self.get_roster() self.send_presence() def _publish(self, msg): diff --git a/examples/register_account.py b/examples/register_account.py index 76b5fcfc..9ab0c664 100755 --- a/examples/register_account.py +++ b/examples/register_account.py @@ -47,7 +47,7 @@ class RegisterBot(slixmpp.ClientXMPP): # for data forms and OOB links that will make that easier. self.add_event_handler("register", self.register) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -61,7 +61,7 @@ class RegisterBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() # We're only concerned about registering, so nothing more to do here. self.disconnect() diff --git a/examples/roster_browser.py b/examples/roster_browser.py index 6ad8b2a4..e9365d09 100755 --- a/examples/roster_browser.py +++ b/examples/roster_browser.py @@ -51,12 +51,8 @@ class RosterBrowser(slixmpp.ClientXMPP): event does not provide any additional data. """ - future = asyncio.Future() - def callback(result): - future.set_result(None) try: - self.get_roster(callback=callback) - await future + await self.get_roster() except IqError as err: print('Error: %s' % err.iq['error']['condition']) except IqTimeout: @@ -138,4 +134,4 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() - xmpp.process() + xmpp.process(forever=False) diff --git a/examples/send_client.py b/examples/send_client.py index 6e3e5865..5d5fb810 100755 --- a/examples/send_client.py +++ b/examples/send_client.py @@ -38,7 +38,7 @@ class SendMsgBot(slixmpp.ClientXMPP): # our roster. self.add_event_handler("session_start", self.start) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -52,7 +52,7 @@ class SendMsgBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() self.send_message(mto=self.recipient, mbody=self.msg, @@ -107,4 +107,4 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() - xmpp.process() + xmpp.process(forever=False) diff --git a/examples/set_avatar.py b/examples/set_avatar.py index 6579b2e7..3188e9d8 100755 --- a/examples/set_avatar.py +++ b/examples/set_avatar.py @@ -46,7 +46,7 @@ class AvatarSetter(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() avatar_file = None try: @@ -137,4 +137,4 @@ if __name__ == '__main__': # Connect to the XMPP server and start processing XMPP stanzas. xmpp.connect() - xmpp.process() + xmpp.process(forever=False) diff --git a/examples/thirdparty_auth.py b/examples/thirdparty_auth.py index 4129fa91..b2623972 100755 --- a/examples/thirdparty_auth.py +++ b/examples/thirdparty_auth.py @@ -60,7 +60,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP): # MUC messages and error messages. self.add_event_handler("message", self.message) - def start(self, event): + async def start(self, event): """ Process the session_start event. @@ -74,7 +74,7 @@ class ThirdPartyAuthBot(slixmpp.ClientXMPP): data. """ self.send_presence() - self.get_roster() + await self.get_roster() def message(self, msg): """ diff --git a/examples/user_location.py b/examples/user_location.py index 7edbeabb..2d473678 100755 --- a/examples/user_location.py +++ b/examples/user_location.py @@ -38,9 +38,9 @@ class LocationBot(ClientXMPP): self.current_tune = None - def start(self, event): + async def start(self, event): self.send_presence() - self.get_roster() + await self.get_roster() self['xep_0115'].update_caps() print("Using freegeoip.net to get geolocation.") diff --git a/examples/user_tune.py b/examples/user_tune.py index 6aa0da8e..2ce29c1d 100755 --- a/examples/user_tune.py +++ b/examples/user_tune.py @@ -35,9 +35,9 @@ class TuneBot(ClientXMPP): self.current_tune = None - def start(self, event): + async def start(self, event): self.send_presence() - self.get_roster() + await self.get_roster() self['xep_0115'].update_caps() def _update_tune(self): @@ -28,9 +28,8 @@ CLASSIFIERS = [ 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Topic :: Internet :: XMPP', 'Topic :: Software Development :: Libraries :: Python Modules', ] 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. |