aboutsummaryrefslogtreecommitdiffstats
path: root/sleekxmpp/xmlstream
diff options
context:
space:
mode:
authorThibaut Horel <thibaut.horel@gmail.com>2010-12-31 19:19:25 +0100
committerThibaut Horel <thibaut.horel@gmail.com>2010-12-31 19:19:25 +0100
commitd90aec17e2201f256783a531c548dcc9857c889d (patch)
tree56b6d0580ee1993c73e67c63d4a452a81bbaaf1e /sleekxmpp/xmlstream
parentaf76bcdf7a947702eaa19d39f5b9ecfcd7ec6fd2 (diff)
downloadalias-d90aec17e2201f256783a531c548dcc9857c889d.tar.gz
Cleanup of repository. Bases of webclient.
* remove sleekxmpp (install guideline in server/README) * move server code to server directory * webclient directory with basic strophejs example
Diffstat (limited to 'sleekxmpp/xmlstream')
-rw-r--r--sleekxmpp/xmlstream/__init__.py19
-rw-r--r--sleekxmpp/xmlstream/filesocket.py41
-rw-r--r--sleekxmpp/xmlstream/handler/__init__.py14
-rw-r--r--sleekxmpp/xmlstream/handler/base.py89
-rw-r--r--sleekxmpp/xmlstream/handler/callback.py84
-rw-r--r--sleekxmpp/xmlstream/handler/waiter.py101
-rw-r--r--sleekxmpp/xmlstream/handler/xmlcallback.py36
-rw-r--r--sleekxmpp/xmlstream/handler/xmlwaiter.py33
-rw-r--r--sleekxmpp/xmlstream/jid.py123
-rw-r--r--sleekxmpp/xmlstream/matcher/__init__.py16
-rw-r--r--sleekxmpp/xmlstream/matcher/base.py34
-rw-r--r--sleekxmpp/xmlstream/matcher/id.py32
-rw-r--r--sleekxmpp/xmlstream/matcher/many.py40
-rw-r--r--sleekxmpp/xmlstream/matcher/stanzapath.py38
-rw-r--r--sleekxmpp/xmlstream/matcher/xmlmask.py159
-rw-r--r--sleekxmpp/xmlstream/matcher/xpath.py79
-rw-r--r--sleekxmpp/xmlstream/scheduler.py207
-rw-r--r--sleekxmpp/xmlstream/stanzabase.py1165
-rw-r--r--sleekxmpp/xmlstream/test.py23
-rw-r--r--sleekxmpp/xmlstream/test.xml2
-rw-r--r--sleekxmpp/xmlstream/testclient.py13
-rw-r--r--sleekxmpp/xmlstream/tostring/__init__.py19
-rw-r--r--sleekxmpp/xmlstream/tostring/tostring.py95
-rw-r--r--sleekxmpp/xmlstream/tostring/tostring26.py101
-rw-r--r--sleekxmpp/xmlstream/xmlstream.py948
25 files changed, 0 insertions, 3511 deletions
diff --git a/sleekxmpp/xmlstream/__init__.py b/sleekxmpp/xmlstream/__init__.py
deleted file mode 100644
index 67b20c5..0000000
--- a/sleekxmpp/xmlstream/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.jid import JID
-from sleekxmpp.xmlstream.scheduler import Scheduler
-from sleekxmpp.xmlstream.stanzabase import StanzaBase, ElementBase, ET
-from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
-from sleekxmpp.xmlstream.tostring import tostring
-from sleekxmpp.xmlstream.xmlstream import XMLStream, RESPONSE_TIMEOUT
-from sleekxmpp.xmlstream.xmlstream import RestartStream
-
-__all__ = ['JID', 'Scheduler', 'StanzaBase', 'ElementBase',
- 'ET', 'StateMachine', 'tostring', 'XMLStream',
- 'RESPONSE_TIMEOUT', 'RestartStream']
diff --git a/sleekxmpp/xmlstream/filesocket.py b/sleekxmpp/xmlstream/filesocket.py
deleted file mode 100644
index 441ff87..0000000
--- a/sleekxmpp/xmlstream/filesocket.py
+++ /dev/null
@@ -1,41 +0,0 @@
-"""
- 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 socket import _fileobject
-import socket
-
-
-class FileSocket(_fileobject):
-
- """
- Create a file object wrapper for a socket to work around
- issues present in Python 2.6 when using sockets as file objects.
-
- The parser for xml.etree.cElementTree requires a file, but we will
- be reading from the XMPP connection socket instead.
- """
-
- def read(self, size=4096):
- """Read data from the socket as if it were a file."""
- data = self._sock.recv(size)
- if data is not None:
- return data
-
-
-class Socket26(socket._socketobject):
-
- """
- A custom socket implementation that uses our own FileSocket class
- to work around issues in Python 2.6 when using sockets as files.
- """
-
- def makefile(self, mode='r', bufsize=-1):
- """makefile([mode[, bufsize]]) -> file object
- Return a regular file object corresponding to the socket. The mode
- and bufsize arguments are as for the built-in open() function."""
- return FileSocket(self._sock, mode, bufsize)
diff --git a/sleekxmpp/xmlstream/handler/__init__.py b/sleekxmpp/xmlstream/handler/__init__.py
deleted file mode 100644
index 7bcf0b7..0000000
--- a/sleekxmpp/xmlstream/handler/__init__.py
+++ /dev/null
@@ -1,14 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.handler.callback import Callback
-from sleekxmpp.xmlstream.handler.waiter import Waiter
-from sleekxmpp.xmlstream.handler.xmlcallback import XMLCallback
-from sleekxmpp.xmlstream.handler.xmlwaiter import XMLWaiter
-
-__all__ = ['Callback', 'Waiter', 'XMLCallback', 'XMLWaiter']
diff --git a/sleekxmpp/xmlstream/handler/base.py b/sleekxmpp/xmlstream/handler/base.py
deleted file mode 100644
index 9c704ec..0000000
--- a/sleekxmpp/xmlstream/handler/base.py
+++ /dev/null
@@ -1,89 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-class BaseHandler(object):
-
- """
- Base class for stream handlers. Stream handlers are matched with
- incoming stanzas so that the stanza may be processed in some way.
- Stanzas may be matched with multiple handlers.
-
- Handler execution may take place in two phases. The first is during
- the stream processing itself. The second is after stream processing
- and during SleekXMPP's main event loop. The prerun method is used
- for execution during stream processing, and the run method is used
- during the main event loop.
-
- Attributes:
- name -- The name of the handler.
- stream -- The stream this handler is assigned to.
-
- Methods:
- match -- Compare a stanza with the handler's matcher.
- prerun -- Handler execution during stream processing.
- run -- Handler execution during the main event loop.
- check_delete -- Indicate if the handler may be removed from use.
- """
-
- def __init__(self, name, matcher, stream=None):
- """
- Create a new stream handler.
-
- Arguments:
- name -- The name of the handler.
- matcher -- A matcher object from xmlstream.matcher that will be
- used to determine if a stanza should be accepted by
- this handler.
- stream -- The XMLStream instance the handler should monitor.
- """
- self.checkDelete = self.check_delete
-
- self.name = name
- self.stream = stream
- self._destroy = False
- self._payload = None
- self._matcher = matcher
- if stream is not None:
- stream.registerHandler(self)
-
- def match(self, xml):
- """
- Compare a stanza or XML object with the handler's matcher.
-
- Arguments
- xml -- An XML or stanza object.
- """
- return self._matcher.match(xml)
-
- def prerun(self, payload):
- """
- Prepare the handler for execution while the XML stream is being
- processed.
-
- Arguments:
- payload -- A stanza object.
- """
- self._payload = payload
-
- def run(self, payload):
- """
- Execute the handler after XML stream processing and during the
- main event loop.
-
- Arguments:
- payload -- A stanza object.
- """
- self._payload = payload
-
- def check_delete(self):
- """
- Check if the handler should be removed from the list of stream
- handlers.
- """
- return self._destroy
diff --git a/sleekxmpp/xmlstream/handler/callback.py b/sleekxmpp/xmlstream/handler/callback.py
deleted file mode 100644
index f0a7285..0000000
--- a/sleekxmpp/xmlstream/handler/callback.py
+++ /dev/null
@@ -1,84 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.handler.base import BaseHandler
-
-
-class Callback(BaseHandler):
-
- """
- The Callback handler will execute a callback function with
- matched stanzas.
-
- The handler may execute the callback either during stream
- processing or during the main event loop.
-
- Callback functions are all executed in the same thread, so be
- aware if you are executing functions that will block for extended
- periods of time. Typically, you should signal your own events using the
- SleekXMPP object's event() method to pass the stanza off to a threaded
- event handler for further processing.
-
- Methods:
- prerun -- Overrides BaseHandler.prerun
- run -- Overrides BaseHandler.run
- """
-
- def __init__(self, name, matcher, pointer, thread=False,
- once=False, instream=False, stream=None):
- """
- Create a new callback handler.
-
- Arguments:
- name -- The name of the handler.
- matcher -- A matcher object for matching stanza objects.
- pointer -- The function to execute during callback.
- thread -- DEPRECATED. Remains only for backwards compatibility.
- once -- Indicates if the handler should be used only
- once. Defaults to False.
- instream -- Indicates if the callback should be executed
- during stream processing instead of in the
- main event loop.
- stream -- The XMLStream instance this handler should monitor.
- """
- BaseHandler.__init__(self, name, matcher, stream)
- self._pointer = pointer
- self._once = once
- self._instream = instream
-
- def prerun(self, payload):
- """
- Execute the callback during stream processing, if
- the callback was created with instream=True.
-
- Overrides BaseHandler.prerun
-
- Arguments:
- payload -- The matched stanza object.
- """
- BaseHandler.prerun(self, payload)
- if self._instream:
- self.run(payload, True)
-
- def run(self, payload, instream=False):
- """
- Execute the callback function with the matched stanza payload.
-
- Overrides BaseHandler.run
-
- Arguments:
- payload -- The matched stanza object.
- instream -- Force the handler to execute during
- stream processing. Used only by prerun.
- Defaults to False.
- """
- if not self._instream or instream:
- BaseHandler.run(self, payload)
- self._pointer(payload)
- if self._once:
- self._destroy = True
diff --git a/sleekxmpp/xmlstream/handler/waiter.py b/sleekxmpp/xmlstream/handler/waiter.py
deleted file mode 100644
index a4bc354..0000000
--- a/sleekxmpp/xmlstream/handler/waiter.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""
- 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
-try:
- import queue
-except ImportError:
- import Queue as queue
-
-from sleekxmpp.xmlstream import StanzaBase, RESPONSE_TIMEOUT
-from sleekxmpp.xmlstream.handler.base import BaseHandler
-
-
-log = logging.getLogger(__name__)
-
-
-class Waiter(BaseHandler):
-
- """
- The Waiter handler allows an event handler to block
- until a particular stanza has been received. The handler
- will either be given the matched stanza, or False if the
- waiter has timed out.
-
- Methods:
- check_delete -- Overrides BaseHandler.check_delete
- prerun -- Overrides BaseHandler.prerun
- run -- Overrides BaseHandler.run
- wait -- Wait for a stanza to arrive and return it to
- an event handler.
- """
-
- def __init__(self, name, matcher, stream=None):
- """
- Create a new Waiter.
-
- Arguments:
- name -- The name of the waiter.
- matcher -- A matcher object to detect the desired stanza.
- stream -- Optional XMLStream instance to monitor.
- """
- BaseHandler.__init__(self, name, matcher, stream=stream)
- self._payload = queue.Queue()
-
- def prerun(self, payload):
- """
- Store the matched stanza.
-
- Overrides BaseHandler.prerun
-
- Arguments:
- payload -- The matched stanza object.
- """
- self._payload.put(payload)
-
- def run(self, payload):
- """
- Do not process this handler during the main event loop.
-
- Overrides BaseHandler.run
-
- Arguments:
- payload -- The matched stanza object.
- """
- pass
-
- def wait(self, timeout=RESPONSE_TIMEOUT):
- """
- Block an event handler while waiting for a stanza to arrive.
-
- Be aware that this will impact performance if called from a
- non-threaded event handler.
-
- Will return either the received stanza, or False if the waiter
- timed out.
-
- Arguments:
- timeout -- The number of seconds to wait for the stanza to
- arrive. Defaults to the global default timeout
- value sleekxmpp.xmlstream.RESPONSE_TIMEOUT.
- """
- try:
- stanza = self._payload.get(True, timeout)
- except queue.Empty:
- stanza = False
- log.warning("Timed out waiting for %s" % self.name)
- self.stream.removeHandler(self.name)
- return stanza
-
- def check_delete(self):
- """
- Always remove waiters after use.
-
- Overrides BaseHandler.check_delete
- """
- return True
diff --git a/sleekxmpp/xmlstream/handler/xmlcallback.py b/sleekxmpp/xmlstream/handler/xmlcallback.py
deleted file mode 100644
index 11607ff..0000000
--- a/sleekxmpp/xmlstream/handler/xmlcallback.py
+++ /dev/null
@@ -1,36 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.handler import Callback
-
-
-class XMLCallback(Callback):
-
- """
- The XMLCallback class is identical to the normal Callback class,
- except that XML contents of matched stanzas will be processed instead
- of the stanza objects themselves.
-
- Methods:
- run -- Overrides Callback.run
- """
-
- def run(self, payload, instream=False):
- """
- Execute the callback function with the matched stanza's
- XML contents, instead of the stanza itself.
-
- Overrides BaseHandler.run
-
- Arguments:
- payload -- The matched stanza object.
- instream -- Force the handler to execute during
- stream processing. Used only by prerun.
- Defaults to False.
- """
- Callback.run(self, payload.xml, instream)
diff --git a/sleekxmpp/xmlstream/handler/xmlwaiter.py b/sleekxmpp/xmlstream/handler/xmlwaiter.py
deleted file mode 100644
index 5201caf..0000000
--- a/sleekxmpp/xmlstream/handler/xmlwaiter.py
+++ /dev/null
@@ -1,33 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.handler import Waiter
-
-
-class XMLWaiter(Waiter):
-
- """
- The XMLWaiter class is identical to the normal Waiter class
- except that it returns the XML contents of the stanza instead
- of the full stanza object itself.
-
- Methods:
- prerun -- Overrides Waiter.prerun
- """
-
- def prerun(self, payload):
- """
- Store the XML contents of the stanza to return to the
- waiting event handler.
-
- Overrides Waiter.prerun
-
- Arguments:
- payload -- The matched stanza object.
- """
- Waiter.prerun(self, payload.xml)
diff --git a/sleekxmpp/xmlstream/jid.py b/sleekxmpp/xmlstream/jid.py
deleted file mode 100644
index 33d845a..0000000
--- a/sleekxmpp/xmlstream/jid.py
+++ /dev/null
@@ -1,123 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-class JID(object):
- """
- A representation of a Jabber ID, or JID.
-
- Each JID may have three components: a user, a domain, and an optional
- resource. For example: user@domain/resource
-
- When a resource is not used, the JID is called a bare JID.
- The JID is a full JID otherwise.
-
- Attributes:
- jid -- Alias for 'full'.
- full -- The value of the full JID.
- bare -- The value of the bare JID.
- user -- The username portion of the JID.
- domain -- The domain name portion of the JID.
- server -- Alias for 'domain'.
- resource -- The resource portion of the JID.
-
- Methods:
- reset -- Use a new JID value.
- regenerate -- Recreate the JID from its components.
- """
-
- def __init__(self, jid):
- """Initialize a new JID"""
- self.reset(jid)
-
- def reset(self, jid):
- """
- Start fresh from a new JID string.
-
- Arguments:
- jid - The new JID value.
- """
- self._full = self._jid = str(jid)
- self._domain = None
- self._resource = None
- self._user = None
- self._bare = None
-
- def __getattr__(self, name):
- """
- Handle getting the JID values, using cache if available.
-
- Arguments:
- name -- One of: user, server, domain, resource,
- full, or bare.
- """
- if name == 'resource':
- if self._resource is None and '/' in self._jid:
- self._resource = self._jid.split('/', 1)[-1]
- return self._resource or ""
- elif name == 'user':
- if self._user is None:
- if '@' in self._jid:
- self._user = self._jid.split('@', 1)[0]
- else:
- self._user = self._user
- return self._user or ""
- elif name in ('server', 'domain', 'host'):
- if self._domain is None:
- self._domain = self._jid.split('@', 1)[-1].split('/', 1)[0]
- return self._domain or ""
- elif name == 'full':
- return self._jid or ""
- elif name == 'bare':
- if self._bare is None:
- self._bare = self._jid.split('/', 1)[0]
- return self._bare or ""
-
- def __setattr__(self, name, value):
- """
- Edit a JID by updating it's individual values, resetting the
- generated JID in the end.
-
- Arguments:
- name -- The name of the JID part. One of: user, domain,
- server, resource, full, jid, or bare.
- value -- The new value for the JID part.
- """
- if name in ('resource', 'user', 'domain'):
- object.__setattr__(self, "_%s" % name, value)
- self.regenerate()
- elif name in ('server', 'domain', 'host'):
- self.domain = value
- elif name in ('full', 'jid'):
- self.reset(value)
- self.regenerate()
- elif name == 'bare':
- if '@' in value:
- u, d = value.split('@', 1)
- object.__setattr__(self, "_user", u)
- object.__setattr__(self, "_domain", d)
- else:
- object.__setattr__(self, "_user", '')
- object.__setattr__(self, "_domain", value)
- self.regenerate()
- else:
- object.__setattr__(self, name, value)
-
- def regenerate(self):
- """Generate a new JID based on current values, useful after editing."""
- jid = ""
- if self.user:
- jid = "%s@" % self.user
- jid += self.domain
- if self.resource:
- jid += "/%s" % self.resource
- self.reset(jid)
-
- def __str__(self):
- """Use the full JID as the string value."""
- return self.full
diff --git a/sleekxmpp/xmlstream/matcher/__init__.py b/sleekxmpp/xmlstream/matcher/__init__.py
deleted file mode 100644
index 1038d1b..0000000
--- a/sleekxmpp/xmlstream/matcher/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.matcher.id import MatcherId
-from sleekxmpp.xmlstream.matcher.many import MatchMany
-from sleekxmpp.xmlstream.matcher.stanzapath import StanzaPath
-from sleekxmpp.xmlstream.matcher.xmlmask import MatchXMLMask
-from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
-
-__all__ = ['MatcherId', 'MatchMany', 'StanzaPath',
- 'MatchXMLMask', 'MatchXPath']
diff --git a/sleekxmpp/xmlstream/matcher/base.py b/sleekxmpp/xmlstream/matcher/base.py
deleted file mode 100644
index 701ab32..0000000
--- a/sleekxmpp/xmlstream/matcher/base.py
+++ /dev/null
@@ -1,34 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-class MatcherBase(object):
-
- """
- Base class for stanza matchers. Stanza matchers are used to pick
- stanzas out of the XML stream and pass them to the appropriate
- stream handlers.
- """
-
- def __init__(self, criteria):
- """
- Create a new stanza matcher.
-
- Arguments:
- criteria -- Object to compare some aspect of a stanza
- against.
- """
- self._criteria = criteria
-
- def match(self, xml):
- """
- Check if a stanza matches the stored criteria.
-
- Meant to be overridden.
- """
- return False
diff --git a/sleekxmpp/xmlstream/matcher/id.py b/sleekxmpp/xmlstream/matcher/id.py
deleted file mode 100644
index 0c8ce2d..0000000
--- a/sleekxmpp/xmlstream/matcher/id.py
+++ /dev/null
@@ -1,32 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-class MatcherId(MatcherBase):
-
- """
- The ID matcher selects stanzas that have the same stanza 'id'
- interface value as the desired ID.
-
- Methods:
- match -- Overrides MatcherBase.match.
- """
-
- def match(self, xml):
- """
- Compare the given stanza's 'id' attribute to the stored
- id value.
-
- Overrides MatcherBase.match.
-
- Arguments:
- xml -- The stanza to compare against.
- """
- return xml['id'] == self._criteria
diff --git a/sleekxmpp/xmlstream/matcher/many.py b/sleekxmpp/xmlstream/matcher/many.py
deleted file mode 100644
index f470ec9..0000000
--- a/sleekxmpp/xmlstream/matcher/many.py
+++ /dev/null
@@ -1,40 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-class MatchMany(MatcherBase):
-
- """
- The MatchMany matcher may compare a stanza against multiple
- criteria. It is essentially an OR relation combining multiple
- matchers.
-
- Each of the criteria must implement a match() method.
-
- Methods:
- match -- Overrides MatcherBase.match.
- """
-
- def match(self, xml):
- """
- Match a stanza against multiple criteria. The match is successful
- if one of the criteria matches.
-
- Each of the criteria must implement a match() method.
-
- Overrides MatcherBase.match.
-
- Arguments:
- xml -- The stanza object to compare against.
- """
- for m in self._criteria:
- if m.match(xml):
- return True
- return False
diff --git a/sleekxmpp/xmlstream/matcher/stanzapath.py b/sleekxmpp/xmlstream/matcher/stanzapath.py
deleted file mode 100644
index f8ff283..0000000
--- a/sleekxmpp/xmlstream/matcher/stanzapath.py
+++ /dev/null
@@ -1,38 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-class StanzaPath(MatcherBase):
-
- """
- The StanzaPath matcher selects stanzas that match a given "stanza path",
- which is similar to a normal XPath except that it uses the interfaces and
- plugins of the stanza instead of the actual, underlying XML.
-
- In most cases, the stanza path and XPath should be identical, but be
- aware that differences may occur.
-
- Methods:
- match -- Overrides MatcherBase.match.
- """
-
- def match(self, stanza):
- """
- Compare a stanza against a "stanza path". A stanza path is similar to
- an XPath expression, but uses the stanza's interfaces and plugins
- instead of the underlying XML. For most cases, the stanza path and
- XPath should be identical, but be aware that differences may occur.
-
- Overrides MatcherBase.match.
-
- Arguments:
- stanza -- The stanza object to compare against.
- """
- return stanza.match(self._criteria)
diff --git a/sleekxmpp/xmlstream/matcher/xmlmask.py b/sleekxmpp/xmlstream/matcher/xmlmask.py
deleted file mode 100644
index 6ebb437..0000000
--- a/sleekxmpp/xmlstream/matcher/xmlmask.py
+++ /dev/null
@@ -1,159 +0,0 @@
-"""
- 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
-
-from xml.parsers.expat import ExpatError
-
-from sleekxmpp.xmlstream.stanzabase import ET
-from sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-# Flag indicating if the builtin XPath matcher should be used, which
-# uses namespaces, or a custom matcher that ignores namespaces.
-# Changing this will affect ALL XMLMask matchers.
-IGNORE_NS = False
-
-
-log = logging.getLogger(__name__)
-
-
-class MatchXMLMask(MatcherBase):
-
- """
- The XMLMask matcher selects stanzas whose XML matches a given
- XML pattern, or mask. For example, message stanzas with body elements
- could be matched using the mask:
-
- <message xmlns="jabber:client"><body /></message>
-
- Use of XMLMask is discouraged, and XPath or StanzaPath should be used
- instead.
-
- The use of namespaces in the mask comparison is controlled by
- IGNORE_NS. Setting IGNORE_NS to True will disable namespace based matching
- for ALL XMLMask matchers.
-
- Methods:
- match -- Overrides MatcherBase.match.
- setDefaultNS -- Set the default namespace for the mask.
- """
-
- def __init__(self, criteria):
- """
- Create a new XMLMask matcher.
-
- Arguments:
- criteria -- Either an XML object or XML string to use as a mask.
- """
- MatcherBase.__init__(self, criteria)
- if isinstance(criteria, str):
- self._criteria = ET.fromstring(self._criteria)
- self.default_ns = 'jabber:client'
-
- def setDefaultNS(self, ns):
- """
- Set the default namespace to use during comparisons.
-
- Arguments:
- ns -- The new namespace to use as the default.
- """
- self.default_ns = ns
-
- def match(self, xml):
- """
- Compare a stanza object or XML object against the stored XML mask.
-
- Overrides MatcherBase.match.
-
- Arguments:
- xml -- The stanza object or XML object to compare against.
- """
- if hasattr(xml, 'xml'):
- xml = xml.xml
- return self._mask_cmp(xml, self._criteria, True)
-
- def _mask_cmp(self, source, mask, use_ns=False, default_ns='__no_ns__'):
- """
- Compare an XML object against an XML mask.
-
- Arguments:
- source -- The XML object to compare against the mask.
- mask -- The XML object serving as the mask.
- use_ns -- Indicates if namespaces should be respected during
- the comparison.
- default_ns -- The default namespace to apply to elements that
- do not have a specified namespace.
- Defaults to "__no_ns__".
- """
- use_ns = not IGNORE_NS
-
- if source is None:
- # If the element was not found. May happend during recursive calls.
- return False
-
- # Convert the mask to an XML object if it is a string.
- if not hasattr(mask, 'attrib'):
- try:
- mask = ET.fromstring(mask)
- except ExpatError:
- log.warning("Expat error: %s\nIn parsing: %s" % ('', mask))
-
- if not use_ns:
- # Compare the element without using namespaces.
- source_tag = source.tag.split('}', 1)[-1]
- mask_tag = mask.tag.split('}', 1)[-1]
- if source_tag != mask_tag:
- return False
- else:
- # Compare the element using namespaces
- mask_ns_tag = "{%s}%s" % (self.default_ns, mask.tag)
- if source.tag not in [mask.tag, mask_ns_tag]:
- return False
-
- # If the mask includes text, compare it.
- if mask.text and source.text != mask.text:
- return False
-
- # Compare attributes. The stanza must include the attributes
- # defined by the mask, but may include others.
- for name, value in mask.attrib.items():
- if source.attrib.get(name, "__None__") != value:
- return False
-
- # Recursively check subelements.
- for subelement in mask:
- if use_ns:
- if not self._mask_cmp(source.find(subelement.tag),
- subelement, use_ns):
- return False
- else:
- if not self._mask_cmp(self._get_child(source, subelement.tag),
- subelement, use_ns):
- return False
-
- # Everything matches.
- return True
-
- def _get_child(self, xml, tag):
- """
- Return a child element given its tag, ignoring namespace values.
-
- Returns None if the child was not found.
-
- Arguments:
- xml -- The XML object to search for the given child tag.
- tag -- The name of the subelement to find.
- """
- tag = tag.split('}')[-1]
- try:
- children = [c.tag.split('}')[-1] for c in xml.getchildren()]
- index = children.index(tag)
- except ValueError:
- return None
- return xml.getchildren()[index]
diff --git a/sleekxmpp/xmlstream/matcher/xpath.py b/sleekxmpp/xmlstream/matcher/xpath.py
deleted file mode 100644
index 669c9f1..0000000
--- a/sleekxmpp/xmlstream/matcher/xpath.py
+++ /dev/null
@@ -1,79 +0,0 @@
-"""
- 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 sleekxmpp.xmlstream.stanzabase import ET
-from sleekxmpp.xmlstream.matcher.base import MatcherBase
-
-
-# Flag indicating if the builtin XPath matcher should be used, which
-# uses namespaces, or a custom matcher that ignores namespaces.
-# Changing this will affect ALL XPath matchers.
-IGNORE_NS = False
-
-
-class MatchXPath(MatcherBase):
-
- """
- The XPath matcher selects stanzas whose XML contents matches a given
- XPath expression.
-
- Note that using this matcher may not produce expected behavior when using
- attribute selectors. For Python 2.6 and 3.1, the ElementTree find method
- does not support the use of attribute selectors. If you need to support
- Python 2.6 or 3.1, it might be more useful to use a StanzaPath matcher.
-
- If the value of IGNORE_NS is set to true, then XPath expressions will
- be matched without using namespaces.
-
- Methods:
- match -- Overrides MatcherBase.match.
- """
-
- def match(self, xml):
- """
- Compare a stanza's XML contents to an XPath expression.
-
- If the value of IGNORE_NS is set to true, then XPath expressions
- will be matched without using namespaces.
-
- Note that in Python 2.6 and 3.1 the ElementTree find method does
- not support attribute selectors in the XPath expression.
-
- Arguments:
- xml -- The stanza object to compare against.
- """
- if hasattr(xml, 'xml'):
- xml = xml.xml
- x = ET.Element('x')
- x.append(xml)
-
- if not IGNORE_NS:
- # Use builtin, namespace respecting, XPath matcher.
- if x.find(self._criteria) is not None:
- return True
- return False
- else:
- # Remove namespaces from the XPath expression.
- criteria = []
- for ns_block in self._criteria.split('{'):
- criteria.extend(ns_block.split('}')[-1].split('/'))
-
- # Walk the XPath expression.
- xml = x
- for tag in criteria:
- if not tag:
- # Skip empty tag name artifacts from the cleanup phase.
- continue
-
- children = [c.tag.split('}')[-1] for c in xml.getchildren()]
- try:
- index = children.index(tag)
- except ValueError:
- return False
- xml = xml.getchildren()[index]
- return True
diff --git a/sleekxmpp/xmlstream/scheduler.py b/sleekxmpp/xmlstream/scheduler.py
deleted file mode 100644
index 1435910..0000000
--- a/sleekxmpp/xmlstream/scheduler.py
+++ /dev/null
@@ -1,207 +0,0 @@
-"""
- 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 time
-import threading
-import logging
-try:
- import queue
-except ImportError:
- import Queue as queue
-
-
-log = logging.getLogger(__name__)
-
-
-class Task(object):
-
- """
- A scheduled task that will be executed by the scheduler
- after a given time interval has passed.
-
- Attributes:
- name -- The name of the task.
- seconds -- The number of seconds to wait before executing.
- callback -- The function to execute.
- args -- The arguments to pass to the callback.
- kwargs -- The keyword arguments to pass to the callback.
- repeat -- Indicates if the task should repeat.
- Defaults to False.
- qpointer -- A pointer to an event queue for queuing callback
- execution instead of executing immediately.
-
- Methods:
- run -- Either queue or execute the callback.
- reset -- Reset the task's timer.
- """
-
- def __init__(self, name, seconds, callback, args=None,
- kwargs=None, repeat=False, qpointer=None):
- """
- Create a new task.
-
- Arguments:
- name -- The name of the task.
- seconds -- The number of seconds to wait before executing.
- callback -- The function to execute.
- args -- The arguments to pass to the callback.
- kwargs -- The keyword arguments to pass to the callback.
- repeat -- Indicates if the task should repeat.
- Defaults to False.
- qpointer -- A pointer to an event queue for queuing callback
- execution instead of executing immediately.
- """
- self.name = name
- self.seconds = seconds
- self.callback = callback
- self.args = args or tuple()
- self.kwargs = kwargs or {}
- self.repeat = repeat
- self.next = time.time() + self.seconds
- self.qpointer = qpointer
-
- def run(self):
- """
- Execute the task's callback.
-
- If an event queue was supplied, place the callback in the queue;
- otherwise, execute the callback immediately.
- """
- if self.qpointer is not None:
- self.qpointer.put(('schedule', self.callback, self.args))
- else:
- self.callback(*self.args, **self.kwargs)
- self.reset()
- return self.repeat
-
- def reset(self):
- """
- Reset the task's timer so that it will repeat.
- """
- self.next = time.time() + self.seconds
-
-
-class Scheduler(object):
-
- """
- A threaded scheduler that allows for updates mid-execution unlike the
- scheduler in the standard library.
-
- http://docs.python.org/library/sched.html#module-sched
-
- Attributes:
- addq -- A queue storing added tasks.
- schedule -- A list of tasks in order of execution times.
- thread -- If threaded, the thread processing the schedule.
- run -- Indicates if the scheduler is running.
- parentqueue -- A parent event queue in control of this scheduler.
-
- Methods:
- add -- Add a new task to the schedule.
- process -- Process and schedule tasks.
- quit -- Stop the scheduler.
- """
-
- def __init__(self, parentqueue=None, parentstop=None):
- """
- Create a new scheduler.
-
- Arguments:
- parentqueue -- A separate event queue controlling this scheduler.
- """
- self.addq = queue.Queue()
- self.schedule = []
- self.thread = None
- self.run = False
- self.parentqueue = parentqueue
- self.parentstop = parentstop
-
- def process(self, threaded=True):
- """
- Begin accepting and processing scheduled tasks.
-
- Arguments:
- threaded -- Indicates if the scheduler should execute in its own
- thread. Defaults to True.
- """
- if threaded:
- self.thread = threading.Thread(name='sheduler_process',
- target=self._process)
- self.thread.start()
- else:
- self._process()
-
- def _process(self):
- """Process scheduled tasks."""
- self.run = True
- try:
- while self.run and (self.parentstop is None or not self.parentstop.isSet()):
- wait = 1
- updated = False
- if self.schedule:
- wait = self.schedule[0].next - time.time()
- try:
- 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 = []
- for task in self.schedule:
- if time.time() >= task.next:
- updated = True
- if not task.run():
- cleanup.append(task)
- else:
- break
- for task in cleanup:
- x = self.schedule.pop(self.schedule.index(task))
- else:
- updated = True
- self.schedule.append(newtask)
- finally:
- if updated:
- self.schedule = sorted(self.schedule,
- key=lambda task: task.next)
- except KeyboardInterrupt:
- self.run = False
- if self.parentstop is not None:
- log.debug("stopping parent")
- self.parentstop.set()
- except SystemExit:
- self.run = False
- if self.parentstop is not None:
- self.parentstop.set()
- log.debug("Quitting Scheduler thread")
- if self.parentqueue is not None:
- self.parentqueue.put(('quit', None, None))
-
- def add(self, name, seconds, callback, args=None,
- kwargs=None, repeat=False, qpointer=None):
- """
- Schedule a new task.
-
- Arguments:
- name -- The name of the task.
- seconds -- The number of seconds to wait before executing.
- callback -- The function to execute.
- args -- The arguments to pass to the callback.
- kwargs -- The keyword arguments to pass to the callback.
- repeat -- Indicates if the task should repeat.
- Defaults to False.
- qpointer -- A pointer to an event queue for queuing callback
- execution instead of executing immediately.
- """
- self.addq.put(Task(name, seconds, callback, args,
- kwargs, repeat, qpointer))
-
- def quit(self):
- """Shutdown the scheduler."""
- self.run = False
diff --git a/sleekxmpp/xmlstream/stanzabase.py b/sleekxmpp/xmlstream/stanzabase.py
deleted file mode 100644
index aabd386..0000000
--- a/sleekxmpp/xmlstream/stanzabase.py
+++ /dev/null
@@ -1,1165 +0,0 @@
-"""
- 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 copy
-import logging
-import sys
-import weakref
-from xml.etree import cElementTree as ET
-
-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'))
-
-
-def register_stanza_plugin(stanza, plugin):
- """
- Associate a stanza object as a plugin for another stanza.
-
- Arguments:
- stanza -- The class of the parent stanza.
- plugin -- The class of the plugin stanza.
- """
- tag = "{%s}%s" % (plugin.namespace, plugin.name)
- stanza.plugin_attrib_map[plugin.plugin_attrib] = plugin
- stanza.plugin_tag_map[tag] = plugin
-
-
-# To maintain backwards compatibility for now, preserve the camel case name.
-registerStanzaPlugin = register_stanza_plugin
-
-
-class ElementBase(object):
-
- """
- The core of SleekXMPP's stanza XML manipulation and handling is provided
- by ElementBase. ElementBase wraps XML cElementTree objects and enables
- access to the XML contents through dictionary syntax, similar in style
- to the Ruby XMPP library Blather's stanza implementation.
-
- Stanzas are defined by their name, namespace, and interfaces. For
- example, a simplistic Message stanza could be defined as:
-
- >>> class Message(ElementBase):
- ... name = "message"
- ... namespace = "jabber:client"
- ... interfaces = set(('to', 'from', 'type', 'body'))
- ... sub_interfaces = set(('body',))
-
- The resulting Message stanza's contents may be accessed as so:
-
- >>> message['to'] = "user@example.com"
- >>> message['body'] = "Hi!"
- >>> message['body']
- "Hi!"
- >>> del message['body']
- >>> message['body']
- ""
-
- The interface values map to either custom access methods, stanza
- XML attributes, or (if the interface is also in sub_interfaces) the
- text contents of a stanza's subelement.
-
- Custom access methods may be created by adding methods of the
- form "getInterface", "setInterface", or "delInterface", where
- "Interface" is the titlecase version of the interface name.
-
- Stanzas may be extended through the use of plugins. A plugin
- is simply a stanza that has a plugin_attrib value. For example:
-
- >>> class MessagePlugin(ElementBase):
- ... name = "custom_plugin"
- ... namespace = "custom"
- ... interfaces = set(('useful_thing', 'custom'))
- ... plugin_attrib = "custom"
-
- The plugin stanza class must be associated with its intended
- container stanza by using register_stanza_plugin as so:
-
- >>> register_stanza_plugin(Message, MessagePlugin)
-
- The plugin may then be accessed as if it were built-in to the parent
- stanza.
-
- >>> message['custom']['useful_thing'] = 'foo'
-
- If a plugin provides an interface that is the same as the plugin's
- plugin_attrib value, then the plugin's interface may be accessed
- directly from the parent stanza, as so:
-
- >>> message['custom'] = 'bar' # Same as using message['custom']['custom']
-
- Class Attributes:
- name -- The name of the stanza's main element.
- namespace -- The namespace of the stanza's main element.
- interfaces -- A set of attribute and element names that may
- be accessed using dictionary syntax.
- sub_interfaces -- A subset of the set of interfaces which map
- to subelements instead of attributes.
- subitem -- A set of stanza classes which are allowed to
- be added as substanzas.
- types -- A set of generic type attribute values.
- plugin_attrib -- The interface name that the stanza uses to be
- accessed as a plugin from another stanza.
- plugin_attrib_map -- A mapping of plugin attribute names with the
- associated plugin stanza classes.
- plugin_tag_map -- A mapping of plugin stanza tag names with
- the associated plugin stanza classes.
-
- Instance Attributes:
- xml -- The stanza's XML contents.
- parent -- The parent stanza of this stanza.
- plugins -- A map of enabled plugin names with the
- initialized plugin stanza objects.
- values -- A dictionary of the stanza's interfaces
- and interface values, including plugins.
-
- Methods:
- setup -- Initialize the stanza's XML contents.
- enable -- Instantiate a stanza plugin.
- Alias for init_plugin.
- init_plugin -- Instantiate a stanza plugin.
- _get_stanza_values -- Return a dictionary of stanza interfaces and
- their values.
- _set_stanza_values -- Set stanza interface values given a dictionary
- of interfaces and values.
- __getitem__ -- Return the value of a stanza interface.
- __setitem__ -- Set the value of a stanza interface.
- __delitem__ -- Remove the value of a stanza interface.
- _set_attr -- Set an attribute value of the main
- stanza element.
- _del_attr -- Remove an attribute from the main
- stanza element.
- _get_attr -- Return an attribute's value from the main
- stanza element.
- _get_sub_text -- Return the text contents of a subelement.
- _set_sub_ext -- Set the text contents of a subelement.
- _del_sub -- Remove a subelement.
- match -- Compare the stanza against an XPath expression.
- find -- Return subelement matching an XPath expression.
- findall -- Return subelements matching an XPath expression.
- get -- Return the value of a stanza interface, with an
- optional default value.
- keys -- Return the set of interface names accepted by
- the stanza.
- append -- Add XML content or a substanza to the stanza.
- appendxml -- Add XML content to the stanza.
- pop -- Remove a substanza.
- next -- Return the next iterable substanza.
- _fix_ns -- Apply the stanza's namespace to non-namespaced
- elements in an XPath expression.
- """
-
- name = 'stanza'
- plugin_attrib = 'plugin'
- namespace = 'jabber:client'
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
- types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
- sub_interfaces = tuple()
- plugin_attrib_map = {}
- plugin_tag_map = {}
- subitem = None
-
- def __init__(self, xml=None, parent=None):
- """
- Create a new stanza object.
-
- Arguments:
- xml -- Initialize the stanza with optional existing XML.
- parent -- Optional stanza object that contains this stanza.
- """
- # To comply with PEP8, method names now use underscores.
- # Deprecated method names are re-mapped for backwards compatibility.
- self.initPlugin = self.init_plugin
- self._getAttr = self._get_attr
- self._setAttr = self._set_attr
- self._delAttr = self._del_attr
- self._getSubText = self._get_sub_text
- self._setSubText = self._set_sub_text
- self._delSub = self._del_sub
- self.getStanzaValues = self._get_stanza_values
- self.setStanzaValues = self._set_stanza_values
-
- self.xml = xml
- self.plugins = {}
- self.iterables = []
- self._index = 0
- if parent is None:
- self.parent = None
- else:
- self.parent = weakref.ref(parent)
-
- ElementBase.values = property(ElementBase._get_stanza_values,
- ElementBase._set_stanza_values)
-
- if self.setup(xml):
- # If we generated our own XML, then everything is ready.
- return
-
- # Initialize values using provided XML
- for child in self.xml.getchildren():
- if child.tag in self.plugin_tag_map:
- plugin = self.plugin_tag_map[child.tag]
- self.plugins[plugin.plugin_attrib] = plugin(child, self)
- if self.subitem is not None:
- for sub in self.subitem:
- if child.tag == "{%s}%s" % (sub.namespace, sub.name):
- self.iterables.append(sub(child, self))
- break
-
- def setup(self, xml=None):
- """
- Initialize the stanza's XML contents.
-
- Will return True if XML was generated according to the stanza's
- definition.
-
- Arguments:
- xml -- Optional XML object to use for the stanza's content
- instead of generating XML.
- """
- if self.xml is None:
- self.xml = xml
-
- if self.xml is None:
- # Generate XML from the stanza definition
- for ename in self.name.split('/'):
- new = ET.Element("{%s}%s" % (self.namespace, ename))
- if self.xml is None:
- self.xml = new
- else:
- last_xml.append(new)
- last_xml = new
- if self.parent is not None:
- self.parent().xml.append(self.xml)
-
- # We had to generate XML
- return True
- else:
- # We did not generate XML
- return False
-
- def enable(self, attrib):
- """
- Enable and initialize a stanza plugin.
-
- Alias for init_plugin.
-
- Arguments:
- attrib -- The stanza interface for the plugin.
- """
- return self.init_plugin(attrib)
-
- def init_plugin(self, attrib):
- """
- Enable and initialize a stanza plugin.
-
- Arguments:
- attrib -- The stanza interface for the plugin.
- """
- if attrib not in self.plugins:
- plugin_class = self.plugin_attrib_map[attrib]
- self.plugins[attrib] = plugin_class(parent=self)
- return self
-
- def _get_stanza_values(self):
- """
- Return a dictionary of the stanza's interface values.
-
- Stanza plugin values are included as nested dictionaries.
- """
- values = {}
- for interface in self.interfaces:
- values[interface] = self[interface]
- for plugin, stanza in self.plugins.items():
- values[plugin] = stanza._get_stanza_values()
- if self.iterables:
- iterables = []
- for stanza in self.iterables:
- iterables.append(stanza._get_stanza_values())
- iterables[-1].update({
- '__childtag__': "{%s}%s" % (stanza.namespace,
- stanza.name)})
- values['substanzas'] = iterables
- return values
-
- def _set_stanza_values(self, values):
- """
- Set multiple stanza interface values using a dictionary.
-
- Stanza plugin values may be set using nested dictionaries.
-
- Arguments:
- values -- A dictionary mapping stanza interface with values.
- Plugin interfaces may accept a nested dictionary that
- will be used recursively.
- """
- for interface, value in values.items():
- if interface == 'substanzas':
- for subdict in value:
- if '__childtag__' in subdict:
- for subclass in self.subitem:
- child_tag = "{%s}%s" % (subclass.namespace,
- subclass.name)
- if subdict['__childtag__'] == child_tag:
- sub = subclass(parent=self)
- sub._set_stanza_values(subdict)
- self.iterables.append(sub)
- break
- elif interface in self.interfaces:
- self[interface] = value
- elif interface in self.plugin_attrib_map:
- if interface not in self.plugins:
- self.init_plugin(interface)
- self.plugins[interface]._set_stanza_values(value)
- return self
-
- def __getitem__(self, attrib):
- """
- Return the value of a stanza interface using dictionary-like syntax.
-
- Example:
- >>> msg['body']
- 'Message contents'
-
- Stanza interfaces are typically mapped directly to the underlying XML
- object, but can be overridden by the presence of a get_attrib method
- (or get_foo where the interface is named foo, etc).
-
- The search order for interface value retrieval for an interface
- named 'foo' is:
- 1. The list of substanzas.
- 2. The result of calling get_foo.
- 3. The result of calling getFoo.
- 4. The contents of the foo subelement, if foo is a sub interface.
- 5. The value of the foo attribute of the XML object.
- 6. The plugin named 'foo'
- 7. An empty string.
-
- Arguments:
- attrib -- The name of the requested stanza interface.
- """
- if attrib == 'substanzas':
- return self.iterables
- elif attrib in self.interfaces:
- get_method = "get_%s" % attrib.lower()
- get_method2 = "get%s" % attrib.title()
- if hasattr(self, get_method):
- return getattr(self, get_method)()
- elif hasattr(self, get_method2):
- return getattr(self, get_method2)()
- else:
- if attrib in self.sub_interfaces:
- return self._get_sub_text(attrib)
- else:
- return self._get_attr(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib not in self.plugins:
- self.init_plugin(attrib)
- return self.plugins[attrib]
- else:
- return ''
-
- def __setitem__(self, attrib, value):
- """
- Set the value of a stanza interface using dictionary-like syntax.
-
- Example:
- >>> msg['body'] = "Hi!"
- >>> msg['body']
- 'Hi!'
-
- Stanza interfaces are typically mapped directly to the underlying XML
- object, but can be overridden by the presence of a set_attrib method
- (or set_foo where the interface is named foo, etc).
-
- The effect of interface value assignment for an interface
- named 'foo' will be one of:
- 1. Delete the interface's contents if the value is None.
- 2. Call set_foo, if it exists.
- 3. Call setFoo, if it exists.
- 4. Set the text of a foo element, if foo is in sub_interfaces.
- 5. Set the value of a top level XML attribute name foo.
- 6. Attempt to pass value to a plugin named foo using the plugin's
- foo interface.
- 7. Do nothing.
-
- Arguments:
- attrib -- The name of the stanza interface to modify.
- value -- The new value of the stanza interface.
- """
- if attrib in self.interfaces:
- if value is not None:
- set_method = "set_%s" % attrib.lower()
- set_method2 = "set%s" % attrib.title()
- if hasattr(self, set_method):
- getattr(self, set_method)(value,)
- elif hasattr(self, set_method2):
- getattr(self, set_method2)(value,)
- else:
- if attrib in self.sub_interfaces:
- return self._set_sub_text(attrib, text=value)
- else:
- self._set_attr(attrib, value)
- else:
- self.__delitem__(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib not in self.plugins:
- self.init_plugin(attrib)
- self.plugins[attrib][attrib] = value
- return self
-
- def __delitem__(self, attrib):
- """
- Delete the value of a stanza interface using dictionary-like syntax.
-
- Example:
- >>> msg['body'] = "Hi!"
- >>> msg['body']
- 'Hi!'
- >>> del msg['body']
- >>> msg['body']
- ''
-
- Stanza interfaces are typically mapped directly to the underlyig XML
- object, but can be overridden by the presence of a del_attrib method
- (or del_foo where the interface is named foo, etc).
-
- The effect of deleting a stanza interface value named foo will be
- one of:
- 1. Call del_foo, if it exists.
- 2. Call delFoo, if it exists.
- 3. Delete foo element, if foo is in sub_interfaces.
- 4. Delete top level XML attribute named foo.
- 5. Remove the foo plugin, if it was loaded.
- 6. Do nothing.
-
- Arguments:
- attrib -- The name of the affected stanza interface.
- """
- if attrib in self.interfaces:
- del_method = "del_%s" % attrib.lower()
- del_method2 = "del%s" % attrib.title()
- if hasattr(self, del_method):
- getattr(self, del_method)()
- elif hasattr(self, del_method2):
- getattr(self, del_method2)()
- else:
- if attrib in self.sub_interfaces:
- return self._del_sub(attrib)
- else:
- self._del_attr(attrib)
- elif attrib in self.plugin_attrib_map:
- if attrib in self.plugins:
- xml = self.plugins[attrib].xml
- del self.plugins[attrib]
- self.xml.remove(xml)
- return self
-
- def _set_attr(self, name, value):
- """
- Set the value of a top level attribute of the underlying XML object.
-
- If the new value is None or an empty string, then the attribute will
- be removed.
-
- Arguments:
- name -- The name of the attribute.
- value -- The new value of the attribute, or None or '' to
- remove it.
- """
- if value is None or value == '':
- self.__delitem__(name)
- else:
- self.xml.attrib[name] = value
-
- def _del_attr(self, name):
- """
- Remove a top level attribute of the underlying XML object.
-
- Arguments:
- name -- The name of the attribute.
- """
- if name in self.xml.attrib:
- del self.xml.attrib[name]
-
- def _get_attr(self, name, default=''):
- """
- Return the value of a top level attribute of the underlying
- XML object.
-
- In case the attribute has not been set, a default value can be
- returned instead. An empty string is returned if no other default
- is supplied.
-
- Arguments:
- name -- The name of the attribute.
- default -- Optional value to return if the attribute has not
- been set. An empty string is returned otherwise.
- """
- return self.xml.attrib.get(name, default)
-
- def _get_sub_text(self, name, default=''):
- """
- Return the text contents of a sub element.
-
- In case the element does not exist, or it has no textual content,
- a default value can be returned instead. An empty string is returned
- if no other default is supplied.
-
- Arguments:
- name -- The name or XPath expression of the element.
- default -- Optional default to return if the element does
- not exists. An empty string is returned otherwise.
- """
- name = self._fix_ns(name)
- stanza = self.xml.find(name)
- if stanza is None or stanza.text is None:
- return default
- else:
- return stanza.text
-
- def _set_sub_text(self, name, text=None, keep=False):
- """
- Set the text contents of a sub element.
-
- In case the element does not exist, a element will be created,
- and its text contents will be set.
-
- If the text is set to an empty string, or None, then the
- element will be removed, unless keep is set to True.
-
- Arguments:
- name -- The name or XPath expression of the element.
- text -- The new textual content of the element. If the text
- is an empty string or None, the element will be removed
- unless the parameter keep is True.
- keep -- Indicates if the element should be kept if its text is
- removed. Defaults to False.
- """
- path = self._fix_ns(name, split=True)
- element = self.xml.find(name)
-
- if not text and not keep:
- return self._del_sub(name)
-
- if element is None:
- # We need to add the element. If the provided name was
- # an XPath expression, some of the intermediate elements
- # may already exist. If so, we want to use those instead
- # of generating new elements.
- last_xml = self.xml
- walked = []
- for ename in path:
- walked.append(ename)
- element = self.xml.find("/".join(walked))
- if element is None:
- element = ET.Element(ename)
- last_xml.append(element)
- last_xml = element
- element = last_xml
-
- element.text = text
- return element
-
- def _del_sub(self, name, all=False):
- """
- Remove sub elements that match the given name or XPath.
-
- If the element is in a path, then any parent elements that become
- empty after deleting the element may also be deleted if requested
- by setting all=True.
-
- Arguments:
- name -- The name or XPath expression for the element(s) to remove.
- all -- If True, remove all empty elements in the path to the
- deleted element. Defaults to False.
- """
- path = self._fix_ns(name, split=True)
- original_target = path[-1]
-
- 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 elements:
- if parent is None:
- parent = self.xml
- for element in elements:
- if element.tag == original_target or \
- not element.getchildren():
- # Only delete the originally requested elements, and
- # any parent elements that have become empty.
- parent.remove(element)
- if not all:
- # If we don't want to delete elements up the tree, stop
- # after deleting the first level of elements.
- return
-
- def match(self, xpath):
- """
- Compare a stanza object with an XPath expression. If the XPath matches
- the contents of the stanza object, the match is successful.
-
- The XPath expression may include checks for stanza attributes.
- For example:
- presence@show=xa@priority=2/status
- Would match a presence stanza whose show value is set to 'xa', has a
- priority value of '2', and has a status element.
-
- Arguments:
- xpath -- The XPath expression to check against. It may be either a
- string or a list of element names with attribute checks.
- """
- if isinstance(xpath, str):
- xpath = self._fix_ns(xpath, split=True, propagate_ns=False)
-
- # Extract the tag name and attribute checks for the first XPath node.
- components = xpath[0].split('@')
- tag = components[0]
- attributes = components[1:]
-
- if tag not in (self.name, "{%s}%s" % (self.namespace, self.name)) and \
- tag not in self.plugins and tag not in self.plugin_attrib:
- # The requested tag is not in this stanza, so no match.
- return False
-
- # Check the rest of the XPath against any substanzas.
- matched_substanzas = False
- for substanza in self.iterables:
- if xpath[1:] == []:
- break
- matched_substanzas = substanza.match(xpath[1:])
- if matched_substanzas:
- break
-
- # Check attribute values.
- for attribute in attributes:
- name, value = attribute.split('=')
- if self[name] != value:
- return False
-
- # Check sub interfaces.
- if len(xpath) > 1:
- next_tag = xpath[1]
- if next_tag in self.sub_interfaces and self[next_tag]:
- return True
-
- # Attempt to continue matching the XPath using the stanza's plugins.
- if not matched_substanzas and len(xpath) > 1:
- # Convert {namespace}tag@attribs to just tag
- next_tag = xpath[1].split('@')[0].split('}')[-1]
- if next_tag in self.plugins:
- return self.plugins[next_tag].match(xpath[1:])
- else:
- return False
-
- # Everything matched.
- return True
-
- def find(self, xpath):
- """
- Find an XML object in this stanza given an XPath expression.
-
- Exposes ElementTree interface for backwards compatibility.
-
- Note that matching on attribute values is not supported in Python 2.6
- or Python 3.1
-
- Arguments:
- xpath -- An XPath expression matching a single desired element.
- """
- return self.xml.find(xpath)
-
- def findall(self, xpath):
- """
- Find multiple XML objects in this stanza given an XPath expression.
-
- Exposes ElementTree interface for backwards compatibility.
-
- Note that matching on attribute values is not supported in Python 2.6
- or Python 3.1.
-
- Arguments:
- xpath -- An XPath expression matching multiple desired elements.
- """
- return self.xml.findall(xpath)
-
- def get(self, key, default=None):
- """
- Return the value of a stanza interface. If the found value is None
- or an empty string, return the supplied default value.
-
- Allows stanza objects to be used like dictionaries.
-
- Arguments:
- key -- The name of the stanza interface to check.
- default -- Value to return if the stanza interface has a value
- of None or "". Will default to returning None.
- """
- value = self[key]
- if value is None or value == '':
- return default
- return value
-
- def keys(self):
- """
- Return the names of all stanza interfaces provided by the
- stanza object.
-
- Allows stanza objects to be used like dictionaries.
- """
- out = []
- out += [x for x in self.interfaces]
- out += [x for x in self.plugins]
- if self.iterables:
- out.append('substanzas')
- return out
-
- def append(self, item):
- """
- Append either an XML object or a substanza to this stanza object.
-
- If a substanza object is appended, it will be added to the list
- of iterable stanzas.
-
- Allows stanza objects to be used like lists.
-
- Arguments:
- item -- Either an XML object or a stanza object to add to
- this stanza's contents.
- """
- if not isinstance(item, ElementBase):
- if type(item) == XML_TYPE:
- return self.appendxml(item)
- else:
- raise TypeError
- self.xml.append(item.xml)
- self.iterables.append(item)
- return self
-
- def appendxml(self, xml):
- """
- Append an XML object to the stanza's XML.
-
- The added XML will not be included in the list of
- iterable substanzas.
-
- Arguments:
- xml -- The XML object to add to the stanza.
- """
- self.xml.append(xml)
- return self
-
- def pop(self, index=0):
- """
- Remove and return the last substanza in the list of
- iterable substanzas.
-
- Allows stanza objects to be used like lists.
-
- Arguments:
- index -- The index of the substanza to remove.
- """
- substanza = self.iterables.pop(index)
- self.xml.remove(substanza.xml)
- return substanza
-
- def next(self):
- """
- Return the next iterable substanza.
- """
- return self.__next__()
-
- @property
- def attrib(self):
- """
- DEPRECATED
-
- For backwards compatibility, stanza.attrib returns the stanza itself.
-
- Older implementations of stanza objects used XML objects directly,
- requiring the use of .attrib to access attribute values.
-
- Use of the dictionary syntax with the stanza object itself for
- accessing stanza interfaces is preferred.
- """
- return self
-
- def _fix_ns(self, xpath, split=False, propagate_ns=True):
- """
- Apply the stanza's namespace to elements in an XPath expression.
-
- Arguments:
- xpath -- The XPath expression to fix with namespaces.
- split -- Indicates if the fixed XPath should be left as a
- list of element names with namespaces. Defaults to
- False, which returns a flat string path.
- propagate_ns -- Overrides propagating parent element namespaces
- to child elements. Useful if you wish to simply
- split an XPath that has non-specified namespaces,
- and child and parent namespaces are known not to
- always match. Defaults to True.
- """
- fixed = []
- # Split the XPath into a series of blocks, where a block
- # is started by an element with a namespace.
- ns_blocks = xpath.split('{')
- for ns_block in ns_blocks:
- if '}' in ns_block:
- # Apply the found namespace to following elements
- # that do not have namespaces.
- namespace = ns_block.split('}')[0]
- elements = ns_block.split('}')[1].split('/')
- else:
- # Apply the stanza's namespace to the following
- # elements since no namespace was provided.
- namespace = self.namespace
- elements = ns_block.split('/')
-
- for element in elements:
- if element:
- # Skip empty entry artifacts from splitting.
- if propagate_ns:
- tag = '{%s}%s' % (namespace, element)
- else:
- tag = element
- fixed.append(tag)
- if split:
- return fixed
- return '/'.join(fixed)
-
- def __eq__(self, other):
- """
- Compare the stanza object with another to test for equality.
-
- Stanzas are equal if their interfaces return the same values,
- and if they are both instances of ElementBase.
-
- Arguments:
- other -- The stanza object to compare against.
- """
- if not isinstance(other, ElementBase):
- return False
-
- # Check that this stanza is a superset of the other stanza.
- values = self._get_stanza_values()
- for key in other.keys():
- if key not in values or values[key] != other[key]:
- return False
-
- # Check that the other stanza is a superset of this stanza.
- values = other._get_stanza_values()
- for key in self.keys():
- if key not in values or values[key] != self[key]:
- return False
-
- # Both stanzas are supersets of each other, therefore they
- # must be equal.
- return True
-
- def __ne__(self, other):
- """
- Compare the stanza object with another to test for inequality.
-
- Stanzas are not equal if their interfaces return different values,
- or if they are not both instances of ElementBase.
-
- Arguments:
- other -- The stanza object to compare against.
- """
- return not self.__eq__(other)
-
- def __bool__(self):
- """
- Stanza objects should be treated as True in boolean contexts.
-
- Python 3.x version.
- """
- return True
-
- def __nonzero__(self):
- """
- Stanza objects should be treated as True in boolean contexts.
-
- Python 2.x version.
- """
- return True
-
- def __len__(self):
- """
- Return the number of iterable substanzas contained in this stanza.
- """
- return len(self.iterables)
-
- def __iter__(self):
- """
- Return an iterator object for iterating over the stanza's substanzas.
-
- The iterator is the stanza object itself. Attempting to use two
- iterators on the same stanza at the same time is discouraged.
- """
- self._index = 0
- return self
-
- def __next__(self):
- """
- Return the next iterable substanza.
- """
- self._index += 1
- if self._index > len(self.iterables):
- self._index = 0
- raise StopIteration
- return self.iterables[self._index - 1]
-
- def __copy__(self):
- """
- Return a copy of the stanza object that does not share the same
- underlying XML object.
- """
- return self.__class__(xml=copy.deepcopy(self.xml), parent=self.parent)
-
- def __str__(self):
- """
- Return a string serialization of the underlying XML object.
- """
- return tostring(self.xml, xmlns='', stanza_ns=self.namespace)
-
- def __repr__(self):
- """
- Use the stanza's serialized XML as its representation.
- """
- return self.__str__()
-
-
-class StanzaBase(ElementBase):
-
- """
- StanzaBase provides the foundation for all other stanza objects used by
- SleekXMPP, and defines a basic set of interfaces common to nearly
- all stanzas. These interfaces are the 'id', 'type', 'to', and 'from'
- attributes. An additional interface, 'payload', is available to access
- the XML contents of the stanza. Most stanza objects will provided more
- specific interfaces, however.
-
- Stanza Interface:
- from -- A JID object representing the sender's JID.
- id -- An optional id value that can be used to associate stanzas
- with their replies.
- payload -- The XML contents of the stanza.
- to -- A JID object representing the recipient's JID.
- type -- The type of stanza, typically will be 'normal', 'error',
- 'get', or 'set', etc.
-
- Attributes:
- stream -- The XMLStream instance that will handle sending this stanza.
- tag -- The namespaced version of the stanza's name.
-
- Methods:
- set_type -- Set the type of the stanza.
- get_to -- Return the stanza recipients JID.
- set_to -- Set the stanza recipient's JID.
- get_from -- Return the stanza sender's JID.
- set_from -- Set the stanza sender's JID.
- get_payload -- Return the stanza's XML contents.
- set_payload -- Append to the stanza's XML contents.
- del_payload -- Remove the stanza's XML contents.
- clear -- Reset the stanza's XML contents.
- reply -- Reset the stanza and modify the 'to' and 'from'
- attributes to prepare for sending a reply.
- error -- Set the stanza's type to 'error'.
- unhandled -- Callback for when the stanza is not handled by a
- stream handler.
- exception -- Callback for if an exception is raised while
- handling the stanza.
- send -- Send the stanza using the stanza's stream.
- """
-
- name = 'stanza'
- namespace = 'jabber:client'
- interfaces = set(('type', 'to', 'from', 'id', 'payload'))
- types = set(('get', 'set', 'error', None, 'unavailable', 'normal', 'chat'))
- sub_interfaces = tuple()
-
- def __init__(self, stream=None, xml=None, stype=None,
- sto=None, sfrom=None, sid=None):
- """
- Create a new stanza.
-
- Arguments:
- stream -- Optional XMLStream responsible for sending this stanza.
- xml -- Optional XML contents to initialize stanza values.
- stype -- Optional stanza type value.
- sto -- Optional string or JID object of the recipient's JID.
- sfrom -- Optional string or JID object of the sender's JID.
- sid -- Optional ID value for the stanza.
- """
- # To comply with PEP8, method names now use underscores.
- # Deprecated method names are re-mapped for backwards compatibility.
- self.setType = self.set_type
- self.getTo = self.get_to
- self.setTo = self.set_to
- self.getFrom = self.get_from
- self.setFrom = self.set_from
- self.getPayload = self.get_payload
- self.setPayload = self.set_payload
- self.delPayload = self.del_payload
-
- self.stream = stream
- if stream is not None:
- self.namespace = stream.default_ns
- ElementBase.__init__(self, xml)
- if stype is not None:
- self['type'] = stype
- if sto is not None:
- self['to'] = sto
- if sfrom is not None:
- self['from'] = sfrom
- self.tag = "{%s}%s" % (self.namespace, self.name)
-
- def set_type(self, value):
- """
- Set the stanza's 'type' attribute.
-
- Only type values contained in StanzaBase.types are accepted.
-
- Arguments:
- value -- One of the values contained in StanzaBase.types
- """
- if value in self.types:
- self.xml.attrib['type'] = value
- return self
-
- def get_to(self):
- """Return the value of the stanza's 'to' attribute."""
- return JID(self._get_attr('to'))
-
- def set_to(self, value):
- """
- Set the 'to' attribute of the stanza.
-
- Arguments:
- value -- A string or JID object representing the recipient's JID.
- """
- return self._set_attr('to', str(value))
-
- def get_from(self):
- """Return the value of the stanza's 'from' attribute."""
- return JID(self._get_attr('from'))
-
- def set_from(self, value):
- """
- Set the 'from' attribute of the stanza.
-
- Arguments:
- from -- A string or JID object representing the sender's JID.
- """
- return self._set_attr('from', str(value))
-
- def get_payload(self):
- """Return a list of XML objects contained in the stanza."""
- return self.xml.getchildren()
-
- def set_payload(self, value):
- """
- Add XML content to the stanza.
-
- Arguments:
- value -- Either an XML or a stanza object, or a list
- of XML or stanza objects.
- """
- if not isinstance(value, list):
- value = [value]
- for val in value:
- self.append(val)
- return self
-
- def del_payload(self):
- """Remove the XML contents of the stanza."""
- self.clear()
- return self
-
- def clear(self):
- """
- Remove all XML element contents and plugins.
-
- Any attribute values will be preserved.
- """
- for child in self.xml.getchildren():
- self.xml.remove(child)
- for plugin in list(self.plugins.keys()):
- del self.plugins[plugin]
- return self
-
- def reply(self):
- """
- Reset the stanza and swap its 'from' and 'to' attributes to prepare
- for sending a reply stanza.
-
- For client streams, the 'from' attribute is removed.
- """
- # if it's a component, use from
- if self.stream and hasattr(self.stream, "is_component") and \
- self.stream.is_component:
- self['from'], self['to'] = self['to'], self['from']
- else:
- self['to'] = self['from']
- del self['from']
- self.clear()
- return self
-
- def error(self):
- """Set the stanza's type to 'error'."""
- self['type'] = 'error'
- return self
-
- def unhandled(self):
- """
- Called when no handlers have been registered to process this
- stanza.
-
- Meant to be overridden.
- """
- pass
-
- def exception(self, e):
- """
- Handle exceptions raised during stanza processing.
-
- Meant to be overridden.
- """
- log.exception('Error handling {%s}%s stanza' % (self.namespace,
- self.name))
-
- def send(self):
- """Queue the stanza to be sent on the XML stream."""
- self.stream.sendRaw(self.__str__())
-
- def __copy__(self):
- """
- Return a copy of the stanza object that does not share the
- same underlying XML object, but does share the same XML stream.
- """
- return self.__class__(xml=copy.deepcopy(self.xml),
- stream=self.stream)
-
- def __str__(self):
- """Serialize the stanza's XML to a string."""
- return tostring(self.xml, xmlns='',
- stanza_ns=self.namespace,
- stream=self.stream)
diff --git a/sleekxmpp/xmlstream/test.py b/sleekxmpp/xmlstream/test.py
deleted file mode 100644
index a45fb8b..0000000
--- a/sleekxmpp/xmlstream/test.py
+++ /dev/null
@@ -1,23 +0,0 @@
-import xmlstream
-import time
-import socket
-from handler.callback import Callback
-from matcher.xpath import MatchXPath
-
-def server():
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- s.bind(('localhost', 5228))
- s.listen(1)
- servers = []
- while True:
- conn, addr = s.accept()
- server = xmlstream.XMLStream(conn, 'localhost', 5228)
- server.registerHandler(Callback('test', MatchXPath('test'), testHandler))
- server.process()
- servers.append(server)
-
-def testHandler(xml):
- print("weeeeeeeee!")
-
-server()
diff --git a/sleekxmpp/xmlstream/test.xml b/sleekxmpp/xmlstream/test.xml
deleted file mode 100644
index d20dd82..0000000
--- a/sleekxmpp/xmlstream/test.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-<stream>
-</stream>
diff --git a/sleekxmpp/xmlstream/testclient.py b/sleekxmpp/xmlstream/testclient.py
deleted file mode 100644
index 50eb6c5..0000000
--- a/sleekxmpp/xmlstream/testclient.py
+++ /dev/null
@@ -1,13 +0,0 @@
-import socket
-import time
-
-s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-s.connect(('localhost', 5228))
-s.send("<stream>")
-#s.flush()
-s.send("<test/>")
-s.send("<test/>")
-s.send("<test/>")
-s.send("</stream>")
-#s.flush()
-s.close()
diff --git a/sleekxmpp/xmlstream/tostring/__init__.py b/sleekxmpp/xmlstream/tostring/__init__.py
deleted file mode 100644
index 5852cba..0000000
--- a/sleekxmpp/xmlstream/tostring/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-"""
- 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 sys
-
-# Import the correct tostring and xml_escape functions based on the Python
-# version in order to properly handle Unicode.
-
-if sys.version_info < (3, 0):
- from sleekxmpp.xmlstream.tostring.tostring26 import tostring, xml_escape
-else:
- from sleekxmpp.xmlstream.tostring.tostring import tostring, xml_escape
-
-__all__ = ['tostring', 'xml_escape']
diff --git a/sleekxmpp/xmlstream/tostring/tostring.py b/sleekxmpp/xmlstream/tostring/tostring.py
deleted file mode 100644
index d8f5c5b..0000000
--- a/sleekxmpp/xmlstream/tostring/tostring.py
+++ /dev/null
@@ -1,95 +0,0 @@
-"""
- SleekXMPP: The Sleek XMPP Library
- Copyright (C) 2010 Nathanael C. Fritz
- This file is part of SleekXMPP.
-
- See the file LICENSE for copying permission.
-"""
-
-
-def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
- """
- Serialize an XML object to a Unicode string.
-
- If namespaces are provided using xmlns or stanza_ns, then elements
- that use those namespaces will not include the xmlns attribute in
- the output.
-
- Arguments:
- xml -- The XML object to serialize. If the value is None,
- then the XML object contained in this stanza
- object will be used.
- xmlns -- Optional namespace of an element wrapping the XML
- object.
- stanza_ns -- The namespace of the stanza object that contains
- the XML object.
- stream -- The XML stream that generated the XML object.
- outbuffer -- Optional buffer for storing serializations during
- recursive calls.
- """
- # Add previous results to the start of the output.
- output = [outbuffer]
-
- # Extract the element's tag name.
- tag_name = xml.tag.split('}', 1)[-1]
-
- # Extract the element's namespace if it is defined.
- if '}' in xml.tag:
- tag_xmlns = xml.tag.split('}', 1)[0][1:]
- else:
- tag_xmlns = ''
-
- # Output the tag name and derived namespace of the element.
- namespace = ''
- if tag_xmlns not in ['', xmlns, stanza_ns]:
- namespace = ' xmlns="%s"' % tag_xmlns
- if stream and tag_xmlns in stream.namespace_map:
- mapped_namespace = stream.namespace_map[tag_xmlns]
- if mapped_namespace:
- tag_name = "%s:%s" % (mapped_namespace, tag_name)
- output.append("<%s" % tag_name)
- output.append(namespace)
-
- # Output escaped attribute values.
- for attrib, value in xml.attrib.items():
- if '{' not in attrib:
- value = xml_escape(value)
- output.append(' %s="%s"' % (attrib, value))
-
- if len(xml) or xml.text:
- # If there are additional child elements to serialize.
- output.append(">")
- if xml.text:
- output.append(xml_escape(xml.text))
- if len(xml):
- for child in xml.getchildren():
- output.append(tostring(child, tag_xmlns, stanza_ns, stream))
- output.append("</%s>" % tag_name)
- elif xml.text:
- # If we only have text content.
- output.append(">%s</%s>" % (xml_escape(xml.text), tag_name))
- else:
- # Empty element.
- output.append(" />")
- if xml.tail:
- # If there is additional text after the element.
- output.append(xml_escape(xml.tail))
- return ''.join(output)
-
-
-def xml_escape(text):
- """
- Convert special characters in XML to escape sequences.
-
- Arguments:
- text -- The XML text to convert.
- """
- text = list(text)
- escapes = {'&': '&amp;',
- '<': '&lt;',
- '>': '&gt;',
- "'": '&apos;',
- '"': '&quot;'}
- for i, c in enumerate(text):
- text[i] = escapes.get(c, c)
- return ''.join(text)
diff --git a/sleekxmpp/xmlstream/tostring/tostring26.py b/sleekxmpp/xmlstream/tostring/tostring26.py
deleted file mode 100644
index 0ee432c..0000000
--- a/sleekxmpp/xmlstream/tostring/tostring26.py
+++ /dev/null
@@ -1,101 +0,0 @@
-"""
- 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 unicode_literals
-import types
-
-
-def tostring(xml=None, xmlns='', stanza_ns='', stream=None, outbuffer=''):
- """
- Serialize an XML object to a Unicode string.
-
- If namespaces are provided using xmlns or stanza_ns, then elements
- that use those namespaces will not include the xmlns attribute in
- the output.
-
- Arguments:
- xml -- The XML object to serialize. If the value is None,
- then the XML object contained in this stanza
- object will be used.
- xmlns -- Optional namespace of an element wrapping the XML
- object.
- stanza_ns -- The namespace of the stanza object that contains
- the XML object.
- stream -- The XML stream that generated the XML object.
- outbuffer -- Optional buffer for storing serializations during
- recursive calls.
- """
- # Add previous results to the start of the output.
- output = [outbuffer]
-
- # Extract the element's tag name.
- tag_name = xml.tag.split('}', 1)[-1]
-
- # Extract the element's namespace if it is defined.
- if '}' in xml.tag:
- tag_xmlns = xml.tag.split('}', 1)[0][1:]
- else:
- tag_xmlns = u''
-
- # Output the tag name and derived namespace of the element.
- namespace = u''
- if tag_xmlns not in ['', xmlns, stanza_ns]:
- namespace = u' xmlns="%s"' % tag_xmlns
- if stream and tag_xmlns in stream.namespace_map:
- mapped_namespace = stream.namespace_map[tag_xmlns]
- if mapped_namespace:
- tag_name = u"%s:%s" % (mapped_namespace, tag_name)
- output.append(u"<%s" % tag_name)
- output.append(namespace)
-
- # Output escaped attribute values.
- for attrib, value in xml.attrib.items():
- if '{' not in attrib:
- value = xml_escape(value)
- output.append(u' %s="%s"' % (attrib, value))
-
- if len(xml) or xml.text:
- # If there are additional child elements to serialize.
- output.append(u">")
- if xml.text:
- output.append(xml_escape(xml.text))
- if len(xml):
- for child in xml.getchildren():
- output.append(tostring(child, tag_xmlns, stanza_ns, stream))
- output.append(u"</%s>" % tag_name)
- elif xml.text:
- # If we only have text content.
- output.append(u">%s</%s>" % (xml_escape(xml.text), tag_name))
- else:
- # Empty element.
- output.append(u" />")
- if xml.tail:
- # If there is additional text after the element.
- output.append(xml_escape(xml.tail))
- return u''.join(output)
-
-
-def xml_escape(text):
- """
- Convert special characters in XML to escape sequences.
-
- Arguments:
- text -- The XML text to convert.
- """
- if type(text) != types.UnicodeType:
- text = list(unicode(text, 'utf-8', 'ignore'))
- else:
- text = list(text)
- escapes = {u'&': u'&amp;',
- u'<': u'&lt;',
- u'>': u'&gt;',
- u"'": u'&apos;',
- u'"': u'&quot;'}
- for i, c in enumerate(text):
- text[i] = escapes.get(c, c)
- return u''.join(text)
diff --git a/sleekxmpp/xmlstream/xmlstream.py b/sleekxmpp/xmlstream/xmlstream.py
deleted file mode 100644
index 30b76ce..0000000
--- a/sleekxmpp/xmlstream/xmlstream.py
+++ /dev/null
@@ -1,948 +0,0 @@
-"""
- 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, unicode_literals
-
-import copy
-import logging
-import socket as Socket
-import ssl
-import sys
-import threading
-import time
-import types
-import signal
-try:
- import queue
-except ImportError:
- import Queue as queue
-
-from sleekxmpp.thirdparty.statemachine import StateMachine
-from sleekxmpp.xmlstream import Scheduler, tostring
-from sleekxmpp.xmlstream.stanzabase import StanzaBase, ET
-
-# In Python 2.x, file socket objects are broken. A patched socket
-# wrapper is provided for this case in filesocket.py.
-if sys.version_info < (3, 0):
- from sleekxmpp.xmlstream.filesocket import FileSocket, Socket26
-
-
-# The time in seconds to wait before timing out waiting for response stanzas.
-RESPONSE_TIMEOUT = 10
-
-# The number of threads to use to handle XML stream events. This is not the
-# same as the number of custom event handling threads. HANDLER_THREADS must
-# be at least 1.
-HANDLER_THREADS = 1
-
-# Flag indicating if the SSL library is available for use.
-SSL_SUPPORT = True
-
-
-log = logging.getLogger(__name__)
-
-
-class RestartStream(Exception):
- """
- Exception to restart stream processing, including
- resending the stream header.
- """
-
-
-class XMLStream(object):
- """
- An XML stream connection manager and event dispatcher.
-
- The XMLStream class abstracts away the issues of establishing a
- connection with a server and sending and receiving XML "stanzas".
- A stanza is a complete XML element that is a direct child of a root
- document element. Two streams are used, one for each communication
- direction, over the same socket. Once the connection is closed, both
- streams should be complete and valid XML documents.
-
- Three types of events are provided to manage the stream:
- Stream -- Triggered based on received stanzas, similar in concept
- to events in a SAX XML parser.
- Custom -- Triggered manually.
- Scheduled -- Triggered based on time delays.
-
- Typically, stanzas are first processed by a stream event handler which
- will then trigger custom events to continue further processing,
- especially since custom event handlers may run in individual threads.
-
-
- Attributes:
- address -- The hostname and port of the server.
- default_ns -- The default XML namespace that will be applied
- to all non-namespaced stanzas.
- event_queue -- A queue of stream, custom, and scheduled
- events to be processed.
- filesocket -- A filesocket created from the main connection socket.
- Required for ElementTree.iterparse.
- namespace_map -- Optional mapping of namespaces to namespace prefixes.
- scheduler -- A scheduler object for triggering events
- after a given period of time.
- 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
- root element.
- stream_header -- The closing tag of the stream's root element.
- use_ssl -- Flag indicating if SSL should be used.
- use_tls -- Flag indicating if TLS should be used.
- stop -- threading Event used to stop all threads.
- auto_reconnect-- Flag to determine whether we auto reconnect.
-
- Methods:
- add_event_handler -- Add a handler for a custom event.
- add_handler -- Shortcut method for registerHandler.
- connect -- Connect to the given server.
- del_event_handler -- Remove a handler for a custom event.
- disconnect -- Disconnect from the server and terminate
- processing.
- event -- Trigger a custom event.
- get_id -- Return the current stream ID.
- incoming_filter -- Optionally filter stanzas before processing.
- new_id -- Generate a new, unique ID value.
- process -- Read XML stanzas from the stream and apply
- matching stream handlers.
- reconnect -- Reestablish a connection to the server.
- register_handler -- Add a handler for a stream event.
- register_stanza -- Add a new stanza object type that may appear
- as a direct child of the stream's root.
- remove_handler -- Remove a stream handler.
- remove_stanza -- Remove a stanza object type.
- schedule -- Schedule an event handler to execute after a
- given delay.
- send -- Send a stanza object on the stream.
- send_raw -- Send a raw string on the stream.
- send_xml -- Send an XML string on the stream.
- set_socket -- Set the stream's socket and generate a new
- filesocket.
- start_stream_handler -- Perform any stream initialization such
- as handshakes.
- start_tls -- Establish a TLS connection and restart
- the stream.
- """
-
- def __init__(self, socket=None, host='', port=0):
- """
- Establish a new XML stream.
-
- Arguments:
- socket -- Use an existing socket for the stream.
- Defaults to None to generate a new socket.
- host -- The name of the target server.
- Defaults to the empty string.
- port -- The port to use for the connection.
- Defaults to 0.
- """
- # To comply with PEP8, method names now use underscores.
- # Deprecated method names are re-mapped for backwards compatibility.
- self.startTLS = self.start_tls
- self.registerStanza = self.register_stanza
- self.removeStanza = self.remove_stanza
- self.registerHandler = self.register_handler
- self.removeHandler = self.remove_handler
- self.setSocket = self.set_socket
- self.sendRaw = self.send_raw
- self.getId = self.get_id
- self.getNewId = self.new_id
- 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')
-
- self.address = (host, int(port))
- self.filesocket = None
- self.set_socket(socket)
-
- if sys.version_info < (3, 0):
- self.socket_class = Socket26
- else:
- self.socket_class = Socket.socket
-
- self.use_ssl = False
- self.use_tls = False
-
- self.default_ns = ''
- self.stream_header = "<stream>"
- self.stream_footer = "</stream>"
-
- self.stop = threading.Event()
- self.stream_end_event = threading.Event()
- self.stream_end_event.set()
- self.event_queue = queue.Queue()
- self.send_queue = queue.Queue()
- self.scheduler = Scheduler(self.event_queue, self.stop)
-
- self.namespace_map = {}
-
- self.__thread = {}
- self.__root_stanza = []
- self.__handlers = []
- self.__event_handlers = {}
- self.__event_handlers_lock = threading.Lock()
-
- self._id = 0
- self._id_lock = threading.Lock()
-
- self.auto_reconnect = True
- self.is_client = False
-
- 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:
- log.debug("Can not set interrupt signal handlers. " + \
- "SleekXMPP is not running from a main thread.")
-
- def _handle_kill(self, signum, frame):
- """
- Capture kill event and disconnect cleanly after first
- spawning the "killed" event.
- """
- self.event("killed", direct=True)
- self.disconnect()
-
- def new_id(self):
- """
- Generate and return a new stream ID in hexadecimal form.
-
- Many stanzas, handlers, or matchers may require unique
- ID values. Using this method ensures that all new ID values
- are unique in this stream.
- """
- with self._id_lock:
- self._id += 1
- return self.get_id()
-
- def get_id(self):
- """
- Return the current unique stream ID in hexadecimal form.
- """
- return "%X" % self._id
-
- def connect(self, host='', port=0, use_ssl=False,
- use_tls=True, reattempt=True):
- """
- Create a new socket and connect to the server.
-
- Setting reattempt to True will cause connection attempts to be made
- every second until a successful connection is established.
-
- Arguments:
- host -- The name of the desired server for the connection.
- port -- Port to connect to on the server.
- use_ssl -- Flag indicating if SSL should be used.
- use_tls -- Flag indicating if TLS should be used.
- reattempt -- Flag indicating if the socket should reconnect
- after disconnections.
- """
- if host and port:
- self.address = (host, int(port))
-
- self.is_client = True
- # Respect previous SSL and TLS usage directives.
- if use_ssl is not None:
- self.use_ssl = use_ssl
- if use_tls is not None:
- self.use_tls = use_tls
-
- # Repeatedly attempt to connect until a successful connection
- # is established.
- connected = self.state.transition('disconnected', 'connected',
- func=self._connect)
- while reattempt and not connected:
- connected = self.state.transition('disconnected', 'connected',
- func=self._connect)
- return connected
-
- def _connect(self):
- self.stop.clear()
- self.socket = self.socket_class(Socket.AF_INET, Socket.SOCK_STREAM)
- self.socket.settimeout(None)
- if self.use_ssl and self.ssl_support:
- 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
- # layer of wrapping.
- self.socket.socket = ssl_socket
- else:
- self.socket = ssl_socket
-
- try:
- 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
- self.event("connected", direct=True)
- return True
- except Socket.error as serr:
- error_msg = "Could not connect to %s:%s. Socket Error #%s: %s"
- log.error(error_msg % (self.address[0], self.address[1],
- serr.errno, serr.strerror))
- time.sleep(1)
- return False
-
- def disconnect(self, reconnect=False):
- """
- Terminate processing and close the XML streams.
-
- Optionally, the connection may be reconnected and
- resume processing afterwards.
-
- Arguments:
- reconnect -- Flag indicating if the connection
- and processing should be restarted.
- Defaults to False.
- """
- self.state.transition('connected', 'disconnected', wait=0.0,
- func=self._disconnect, args=(reconnect,))
-
- def _disconnect(self, reconnect=False):
- # Send the end of stream marker.
- self.send_raw(self.stream_footer)
- # Wait for confirmation that the stream was
- # closed in the other direction.
- if not reconnect:
- self.auto_reconnect = False
- self.stream_end_event.wait(4)
- if not self.auto_reconnect:
- self.stop.set()
- try:
- self.socket.close()
- self.filesocket.close()
- self.socket.shutdown(Socket.SHUT_RDWR)
- except Socket.error as serr:
- pass
- finally:
- #clear your application state
- self.event("disconnected", direct=True)
- return True
-
- def reconnect(self):
- """
- Reset the stream's state and reconnect to the server.
- """
- log.debug("reconnecting...")
- self.state.transition('connected', 'disconnected', wait=2.0,
- func=self._disconnect, args=(True,))
- log.debug("connecting...")
- return self.state.transition('disconnected', 'connected',
- wait=2.0, func=self._connect)
-
- def set_socket(self, socket, ignore=False):
- """
- Set the socket to use for the stream.
-
- The filesocket will be recreated as well.
-
- Arguments:
- socket -- The new socket to use.
- ignore -- don't set the state
- """
- self.socket = socket
- if socket is not None:
- # ElementTree.iterparse requires a file.
- # 0 buffer files have to be binary.
-
- # Use the correct fileobject type based on the Python
- # version to work around a broken implementation in
- # Python 2.x.
- if sys.version_info < (3, 0):
- self.filesocket = FileSocket(self.socket)
- else:
- self.filesocket = self.socket.makefile('rb', 0)
- if not ignore:
- self.state._set_state('connected')
-
- def start_tls(self):
- """
- Perform handshakes for TLS.
-
- If the handshake is successful, the XML stream will need
- to be restarted.
- """
- if self.ssl_support:
- 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)
- if hasattr(self.socket, 'socket'):
- # We are using a testing socket, so preserve the top
- # layer of wrapping.
- self.socket.socket = ssl_socket
- else:
- self.socket = ssl_socket
- self.socket.do_handshake()
- self.set_socket(self.socket)
- return True
- else:
- log.warning("Tried to enable TLS, but ssl module not found.")
- return False
-
- def start_stream_handler(self, xml):
- """
- Perform any initialization actions, such as handshakes, once the
- stream header has been sent.
-
- Meant to be overridden.
- """
- pass
-
- def register_stanza(self, stanza_class):
- """
- Add a stanza object class as a known root stanza. A root stanza is
- one that appears as a direct child of the stream's root element.
-
- Stanzas that appear as substanzas of a root stanza do not need to
- be registered here. That is done using register_stanza_plugin() from
- sleekxmpp.xmlstream.stanzabase.
-
- Stanzas that are not registered will not be converted into
- stanza objects, but may still be processed using handlers and
- matchers.
-
- Arguments:
- stanza_class -- The top-level stanza object's class.
- """
- self.__root_stanza.append(stanza_class)
-
- def remove_stanza(self, stanza_class):
- """
- Remove a stanza from being a known root stanza. A root stanza is
- one that appears as a direct child of the stream's root element.
-
- Stanzas that are not registered will not be converted into
- stanza objects, but may still be processed using handlers and
- matchers.
- """
- del self.__root_stanza[stanza_class]
-
- def add_handler(self, mask, pointer, name=None, disposable=False,
- threaded=False, filter=False, instream=False):
- """
- A shortcut method for registering a handler using XML masks.
-
- Arguments:
- mask -- An XML snippet matching the structure of the
- stanzas that will be passed to this handler.
- pointer -- The handler function itself.
- name -- A unique name for the handler. A name will
- be generated if one is not provided.
- disposable -- Indicates if the handler should be discarded
- after one use.
- threaded -- Deprecated. Remains for backwards compatibility.
- filter -- Deprecated. Remains for backwards compatibility.
- instream -- Indicates if the handler should execute during
- stream processing and not during normal event
- processing.
- """
- # To prevent circular dependencies, we must load the matcher
- # and handler classes here.
- from sleekxmpp.xmlstream.matcher import MatchXMLMask
- from sleekxmpp.xmlstream.handler import XMLCallback
-
- if name is None:
- name = 'add_handler_%s' % self.getNewId()
- self.registerHandler(XMLCallback(name, MatchXMLMask(mask), pointer,
- once=disposable, instream=instream))
-
- def register_handler(self, handler, before=None, after=None):
- """
- Add a stream event handler that will be executed when a matching
- stanza is received.
-
- Arguments:
- handler -- The handler object to execute.
- """
- if handler.stream is None:
- self.__handlers.append(handler)
- handler.stream = self
-
- def remove_handler(self, name):
- """
- Remove any stream event handlers with the given name.
-
- Arguments:
- name -- The name of the handler.
- """
- idx = 0
- for handler in self.__handlers:
- if handler.name == name:
- self.__handlers.pop(idx)
- return True
- idx += 1
- return False
-
- def add_event_handler(self, name, pointer,
- threaded=False, disposable=False):
- """
- Add a custom event handler that will be executed whenever
- its event is manually triggered.
-
- Arguments:
- name -- The name of the event that will trigger
- this handler.
- pointer -- The function to execute.
- threaded -- If set to True, the handler will execute
- in its own thread. Defaults to False.
- disposable -- If set to True, the handler will be
- discarded after one use. Defaults to False.
- """
- if not name in self.__event_handlers:
- self.__event_handlers[name] = []
- self.__event_handlers[name].append((pointer, threaded, disposable))
-
- def del_event_handler(self, name, pointer):
- """
- Remove a function as a handler for an event.
-
- Arguments:
- name -- The name of the event.
- pointer -- The function to remove as a handler.
- """
- if not name in self.__event_handlers:
- return
-
- # Need to keep handlers that do not use
- # the given function pointer
- def filter_pointers(handler):
- return handler[0] != pointer
-
- 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.
-
- Arguments:
- 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
- event queue. All event handlers will run in the
- same thread.
- """
- for handler in self.__event_handlers.get(name, []):
- if direct:
- try:
- handler[0](copy.copy(data))
- except Exception as e:
- error_msg = 'Error processing event handler: %s'
- log.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
- # processed in the queue.
- with self.__event_handlers_lock:
- try:
- h_index = self.__event_handlers[name].index(handler)
- self.__event_handlers[name].pop(h_index)
- except:
- pass
-
- def schedule(self, name, seconds, callback, args=None,
- kwargs=None, repeat=False):
- """
- Schedule a callback function to execute after a given delay.
-
- Arguments:
- name -- A unique name for the scheduled callback.
- seconds -- The time in seconds to wait before executing.
- callback -- A pointer to the function to execute.
- args -- A tuple of arguments to pass to the function.
- kwargs -- A dictionary of keyword arguments to pass to
- the function.
- repeat -- Flag indicating if the scheduled event should
- be reset and repeat after executing.
- """
- self.scheduler.add(name, seconds, callback, args, kwargs,
- repeat, qpointer=self.event_queue)
-
- def incoming_filter(self, xml):
- """
- Filter incoming XML objects before they are processed.
-
- Possible uses include remapping namespaces, or correcting elements
- from sources with incorrect behavior.
-
- Meant to be overridden.
- """
- return xml
-
- def send(self, data, mask=None, timeout=RESPONSE_TIMEOUT):
- """
- A wrapper for send_raw for sending stanza objects.
-
- May optionally block until an expected response is received.
-
- Arguments:
- data -- The stanza object to send on the stream.
- mask -- Deprecated. An XML snippet matching the structure
- of the expected response. Execution will block
- in this thread until the response is received
- or a timeout occurs.
- timeout -- Time in seconds to wait for a response before
- continuing. Defaults to RESPONSE_TIMEOUT.
- """
- if hasattr(mask, 'xml'):
- mask = mask.xml
- data = str(data)
- if mask is not None:
- log.warning("Use of send mask waiters is deprecated.")
- wait_for = Waiter("SendWait_%s" % self.new_id(),
- MatchXMLMask(mask))
- self.register_handler(wait_for)
- self.send_raw(data)
- if mask is not None:
- return wait_for.wait(timeout)
-
- def send_raw(self, data):
- """
- Send raw data across the stream.
-
- Arguments:
- data -- Any string value.
- """
- self.send_queue.put(data)
- return True
-
- def send_xml(self, data, mask=None, timeout=RESPONSE_TIMEOUT):
- """
- Send an XML object on the stream, and optionally wait
- for a response.
-
- Arguments:
- data -- The XML object to send on the stream.
- mask -- Deprecated. An XML snippet matching the structure
- of the expected response. Execution will block
- in this thread until the response is received
- or a timeout occurs.
- timeout -- Time in seconds to wait for a response before
- continuing. Defaults to RESPONSE_TIMEOUT.
- """
- return self.send(tostring(data), mask, timeout)
-
- def process(self, threaded=True):
- """
- Initialize the XML streams and begin processing events.
-
- The number of threads used for processing stream events is determined
- by HANDLER_THREADS.
-
- Arguments:
- threaded -- If threaded=True then event dispatcher will run
- in a separate thread, allowing for the stream to be
- used in the background for another application.
- Defaults to True.
-
- Event handlers and the send queue will be threaded
- regardless of this parameter's value.
- """
- self.scheduler.process(threaded=True)
-
- def start_thread(name, target):
- self.__thread[name] = threading.Thread(name=name, target=target)
- self.__thread[name].start()
-
- for t in range(0, HANDLER_THREADS):
- log.debug("Starting HANDLER THREAD")
- start_thread('stream_event_handler_%s' % t, self._event_runner)
-
- start_thread('send_thread', self._send_thread)
-
- if threaded:
- # Run the XML stream in the background for another application.
- start_thread('process', self._process)
- else:
- self._process()
-
- def _process(self):
- """
- Start processing the XML streams.
-
- Processing will continue after any recoverable errors
- if reconnections are allowed.
- """
- firstrun = True
-
- # The body of this loop will only execute once per connection.
- # Additional passes will be made only if an error occurs and
- # reconnecting is permitted.
- while firstrun or (self.auto_reconnect and not self.stop.isSet()):
- firstrun = False
- try:
- if self.is_client:
- self.send_raw(self.stream_header)
- # The call to self.__read_xml will block and prevent
- # the body of the loop from running until a disconnect
- # occurs. After any reconnection, the stream header will
- # be resent and processing will resume.
- while not self.stop.isSet() and self.__read_xml():
- # Ensure the stream header is sent for any
- # new connections.
- if self.is_client:
- self.send_raw(self.stream_header)
- except KeyboardInterrupt:
- log.debug("Keyboard Escape Detected in _process")
- self.stop.set()
- except SystemExit:
- log.debug("SystemExit in _process")
- self.stop.set()
- except Socket.error:
- log.exception('Socket Error')
- except:
- if not self.stop.isSet():
- log.exception('Connection error.')
- if not self.stop.isSet() and self.auto_reconnect:
- self.reconnect()
- else:
- self.disconnect()
- self.event_queue.put(('quit', None, None))
- self.scheduler.run = False
-
- def __read_xml(self):
- """
- Parse the incoming XML stream, raising stream events for
- each received stanza.
- """
- depth = 0
- root = None
- for (event, xml) in ET.iterparse(self.filesocket, (b'end', b'start')):
- if event == b'start':
- if depth == 0:
- # We have received the start of the root element.
- root = xml
- # Perform any stream initialization actions, such
- # as handshakes.
- self.stream_end_event.clear()
- self.start_stream_handler(root)
- depth += 1
- if event == b'end':
- depth -= 1
- if depth == 0:
- # The stream's root element has closed,
- # terminating the stream.
- log.debug("End of stream recieved")
- self.stream_end_event.set()
- return False
- elif depth == 1:
- # We only raise events for stanzas that are direct
- # children of the root element.
- try:
- self.__spawn_event(xml)
- except RestartStream:
- return True
- if root:
- # Keep the root element empty of children to
- # save on memory use.
- root.clear()
- log.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
- objects if applicable and queue stream events to be processed
- by matching handlers.
-
- Arguments:
- xml -- The XML stanza to analyze.
- """
- log.debug("RECV: %s" % tostring(xml,
- xmlns=self.default_ns,
- stream=self))
- # Apply any preprocessing filters.
- xml = self.incoming_filter(xml)
-
- # Convert the raw XML object into a stanza object. If no registered
- # stanza type applies, a generic StanzaBase stanza will be used.
- stanza_type = StanzaBase
- for stanza_class in self.__root_stanza:
- if xml.tag == "{%s}%s" % (self.default_ns, stanza_class.name):
- stanza_type = stanza_class
- break
- stanza = stanza_type(self, xml)
-
- # Match the stanza against registered handlers. Handlers marked
- # to run "in stream" will be executed immediately; the rest will
- # be queued.
- unhandled = True
- for handler in self.__handlers:
- if handler.match(stanza):
- stanza_copy = stanza_type(self, copy.deepcopy(xml))
- handler.prerun(stanza_copy)
- self.event_queue.put(('stanza', handler, stanza_copy))
- try:
- if handler.check_delete():
- self.__handlers.pop(self.__handlers.index(handler))
- except:
- pass # not thread safe
- unhandled = False
-
- # Some stanzas require responses, such as Iq queries. A default
- # handler will be executed immediately for this case.
- if unhandled:
- stanza.unhandled()
-
- def _threaded_event_wrapper(self, func, args):
- """
- Capture exceptions for event handlers that run
- in individual threads.
-
- Arguments:
- func -- The event handler to execute.
- args -- Arguments to the event handler.
- """
- try:
- func(*args)
- except Exception as e:
- error_msg = 'Error processing event handler: %s'
- log.exception(error_msg % str(func))
- if hasattr(args[0], 'exception'):
- args[0].exception(e)
-
- def _event_runner(self):
- """
- Process the event queue and execute handlers.
-
- The number of event runner threads is controlled by HANDLER_THREADS.
-
- Stream event handlers will all execute in this thread. Custom event
- handlers may be spawned in individual threads.
- """
- log.debug("Loading event runner")
- try:
- while not self.stop.isSet():
- try:
- event = self.event_queue.get(True, timeout=5)
- except queue.Empty:
- event = None
- if event is None:
- continue
-
- etype, handler = event[0:2]
- args = event[2:]
-
- if etype == 'stanza':
- try:
- handler.run(args[0])
- except Exception as e:
- error_msg = 'Error processing stream handler: %s'
- log.exception(error_msg % handler.name)
- args[0].exception(e)
- elif etype == 'schedule':
- try:
- log.debug(args)
- handler(*args[0])
- except:
- log.exception('Error processing scheduled task')
- elif etype == 'event':
- func, threaded, disposable = handler
- try:
- if threaded:
- x = threading.Thread(
- name="Event_%s" % str(func),
- target=self._threaded_event_wrapper,
- args=(func, args))
- x.start()
- else:
- func(*args)
- except Exception as e:
- error_msg = 'Error processing event handler: %s'
- log.exception(error_msg % str(func))
- if hasattr(args[0], 'exception'):
- args[0].exception(e)
- elif etype == 'quit':
- log.debug("Quitting event runner thread")
- return False
- except KeyboardInterrupt:
- log.debug("Keyboard Escape Detected in _event_runner")
- self.disconnect()
- return
- except SystemExit:
- self.disconnect()
- self.event_queue.put(('quit', None, None))
- return
-
- def _send_thread(self):
- """
- Extract stanzas from the send queue and send them on the stream.
- """
- try:
- while not self.stop.isSet():
- try:
- data = self.send_queue.get(True, 1)
- except queue.Empty:
- continue
- log.debug("SEND: %s" % data)
- try:
- self.socket.send(data.encode('utf-8'))
- except:
- log.warning("Failed to send %s" % data)
- self.disconnect(self.auto_reconnect)
- except KeyboardInterrupt:
- log.debug("Keyboard Escape Detected in _send_thread")
- self.disconnect()
- return
- except SystemExit:
- self.disconnect()
- self.event_queue.put(('quit', None, None))
- return