2012-03-31 06:02:48 +00:00
|
|
|
from sleekxmpp.xmlstream import JID
|
|
|
|
|
|
|
|
|
|
|
|
class APIWrapper(object):
|
|
|
|
|
|
|
|
def __init__(self, api, name):
|
|
|
|
self.api = api
|
|
|
|
self.name = name
|
|
|
|
|
|
|
|
def __getattr__(self, attr):
|
|
|
|
"""Curry API management commands with the API name."""
|
|
|
|
if attr == 'name':
|
|
|
|
return self.name
|
|
|
|
elif attr == 'settings':
|
|
|
|
return self.api.settings[self.name]
|
|
|
|
elif attr == 'register':
|
2012-04-03 04:55:48 +00:00
|
|
|
def curried_handler(handler, op, jid=None, node=None, default=False):
|
2012-03-31 06:02:48 +00:00
|
|
|
register = getattr(self.api, attr)
|
2012-04-03 04:55:48 +00:00
|
|
|
return register(handler, self.name, op, jid, node, default)
|
2012-03-31 06:02:48 +00:00
|
|
|
return curried_handler
|
|
|
|
elif attr == 'register_default':
|
|
|
|
def curried_handler(handler, op, jid=None, node=None):
|
|
|
|
return getattr(self.api, attr)(handler, self.name, op)
|
|
|
|
return curried_handler
|
|
|
|
elif attr in ('run', 'restore_default', 'unregister'):
|
|
|
|
def curried_handler(*args, **kwargs):
|
|
|
|
return getattr(self.api, attr)(self.name, *args, **kwargs)
|
|
|
|
return curried_handler
|
|
|
|
return None
|
|
|
|
|
|
|
|
def __getitem__(self, attr):
|
|
|
|
def curried_handler(jid=None, node=None, ifrom=None, args=None):
|
|
|
|
return self.api.run(self.name, attr, jid, node, ifrom, args)
|
|
|
|
return curried_handler
|
|
|
|
|
|
|
|
|
|
|
|
class APIRegistry(object):
|
|
|
|
|
|
|
|
def __init__(self, xmpp):
|
|
|
|
self._handlers = {}
|
|
|
|
self._handler_defaults = {}
|
|
|
|
self.xmpp = xmpp
|
|
|
|
self.settings = {}
|
|
|
|
|
|
|
|
def _setup(self, ctype, op):
|
|
|
|
"""Initialize the API callback dictionaries.
|
|
|
|
|
|
|
|
:param string ctype: The name of the API to initialize.
|
|
|
|
:param string op: The API operation to initialize.
|
|
|
|
"""
|
|
|
|
if ctype not in self.settings:
|
|
|
|
self.settings[ctype] = {}
|
|
|
|
if ctype not in self._handler_defaults:
|
|
|
|
self._handler_defaults[ctype] = {}
|
|
|
|
if ctype not in self._handlers:
|
|
|
|
self._handlers[ctype] = {}
|
|
|
|
if op not in self._handlers[ctype]:
|
|
|
|
self._handlers[ctype][op] = {'global': None,
|
|
|
|
'jid': {},
|
|
|
|
'node': {}}
|
|
|
|
|
|
|
|
def wrap(self, ctype):
|
|
|
|
"""Return a wrapper object that targets a specific API."""
|
|
|
|
return APIWrapper(self, ctype)
|
|
|
|
|
|
|
|
def purge(self, ctype):
|
|
|
|
"""Remove all information for a given API."""
|
|
|
|
del self.settings[ctype]
|
|
|
|
del self._handler_defaults[ctype]
|
|
|
|
del self._handlers[ctype]
|
|
|
|
|
|
|
|
def run(self, ctype, op, jid=None, node=None, ifrom=None, args=None):
|
|
|
|
"""Execute an API callback, based on specificity.
|
|
|
|
|
|
|
|
The API callback that is executed is chosen based on the combination
|
|
|
|
of the provided JID and node:
|
|
|
|
|
|
|
|
JID | node | Handler
|
|
|
|
==============================
|
|
|
|
Given | Given | Node handler
|
|
|
|
Given | None | JID handler
|
|
|
|
None | None | Global handler
|
|
|
|
|
|
|
|
A node handler is responsible for servicing a single node at a single
|
|
|
|
JID, while a JID handler may respond for any node at a given JID, and
|
|
|
|
the global handler will answer to any JID+node combination.
|
|
|
|
|
|
|
|
Handlers should check that the JID ``ifrom`` is authorized to perform
|
|
|
|
the desired action.
|
|
|
|
|
|
|
|
:param string ctype: The name of the API to use.
|
|
|
|
:param string op: The API operation to perform.
|
|
|
|
:param JID jid: Optionally provide specific JID.
|
|
|
|
:param string node: Optionally provide specific node.
|
|
|
|
:param JID ifrom: Optionally provide the requesting JID.
|
|
|
|
:param tuple args: Optional positional arguments to the handler.
|
|
|
|
"""
|
|
|
|
self._setup(ctype, op)
|
|
|
|
|
|
|
|
if jid in (None, ''):
|
|
|
|
jid = self.xmpp.boundjid
|
|
|
|
if jid and not isinstance(jid, JID):
|
|
|
|
jid = JID(jid)
|
|
|
|
|
|
|
|
if node is None:
|
|
|
|
node = ''
|
|
|
|
|
|
|
|
if self.xmpp.is_component:
|
|
|
|
if self.settings[ctype].get('component_bare', False):
|
|
|
|
jid = jid.bare
|
|
|
|
else:
|
|
|
|
jid = jid.full
|
|
|
|
else:
|
|
|
|
if self.settings[ctype].get('client_bare', True):
|
|
|
|
jid = jid.bare
|
|
|
|
else:
|
|
|
|
jid = jid.full
|
|
|
|
|
2012-04-06 19:22:36 +00:00
|
|
|
jid = JID(jid)
|
|
|
|
|
2012-03-31 06:02:48 +00:00
|
|
|
handler = self._handlers[ctype][op]['node'].get((jid, node), None)
|
|
|
|
if handler is None:
|
|
|
|
handler = self._handlers[ctype][op]['jid'].get(jid, None)
|
|
|
|
if handler is None:
|
|
|
|
handler = self._handlers[ctype][op].get('global', None)
|
|
|
|
|
|
|
|
if handler:
|
|
|
|
try:
|
|
|
|
return handler(jid, node, ifrom, args)
|
|
|
|
except TypeError:
|
|
|
|
# To preserve backward compatibility, drop the ifrom
|
|
|
|
# parameter for existing handlers that don't understand it.
|
|
|
|
return handler(jid, node, args)
|
|
|
|
|
|
|
|
def register(self, handler, ctype, op, jid=None, node=None, default=False):
|
|
|
|
"""Register an API callback, with JID+node specificity.
|
|
|
|
|
|
|
|
The API callback can later be executed based on the
|
|
|
|
specificity of the provided JID+node combination.
|
|
|
|
|
|
|
|
See :meth:`~ApiRegistry.run` for more details.
|
|
|
|
|
|
|
|
:param string ctype: The name of the API to use.
|
|
|
|
:param string op: The API operation to perform.
|
|
|
|
:param JID jid: Optionally provide specific JID.
|
|
|
|
:param string node: Optionally provide specific node.
|
|
|
|
"""
|
|
|
|
self._setup(ctype, op)
|
|
|
|
if jid is None and node is None:
|
|
|
|
if handler is None:
|
|
|
|
handler = self._handler_defaults[op]
|
|
|
|
self._handlers[ctype][op]['global'] = handler
|
|
|
|
elif jid is not None and node is None:
|
|
|
|
self._handlers[ctype][op]['jid'][jid] = handler
|
|
|
|
else:
|
|
|
|
self._handlers[ctype][op]['node'][(jid, node)] = handler
|
|
|
|
|
2012-04-03 04:55:48 +00:00
|
|
|
if default:
|
|
|
|
self.register_default(handler, ctype, op)
|
|
|
|
|
2012-03-31 06:02:48 +00:00
|
|
|
def register_default(self, handler, ctype, op):
|
|
|
|
"""Register a default, global handler for an operation.
|
|
|
|
|
|
|
|
:param func handler: The default, global handler for the operation.
|
|
|
|
:param string ctype: The name of the API to modify.
|
|
|
|
:param string op: The API operation to use.
|
|
|
|
"""
|
|
|
|
self._setup(ctype, op)
|
|
|
|
self._handler_defaults[ctype][op] = handler
|
|
|
|
|
|
|
|
def unregister(self, ctype, op, jid=None, node=None):
|
|
|
|
"""Remove an API callback.
|
|
|
|
|
|
|
|
The API callback chosen for removal is based on the
|
|
|
|
specificity of the provided JID+node combination.
|
|
|
|
|
|
|
|
See :meth:`~ApiRegistry.run` for more details.
|
|
|
|
|
|
|
|
:param string ctype: The name of the API to use.
|
|
|
|
:param string op: The API operation to perform.
|
|
|
|
:param JID jid: Optionally provide specific JID.
|
|
|
|
:param string node: Optionally provide specific node.
|
|
|
|
"""
|
|
|
|
self._setup(ctype, op)
|
|
|
|
self.register(None, ctype, op, jid, node)
|
|
|
|
|
|
|
|
def restore_default(self, ctype, op, jid=None, node=None):
|
|
|
|
"""Reset an API callback to use a default handler.
|
|
|
|
|
|
|
|
:param string ctype: The name of the API to use.
|
|
|
|
:param string op: The API operation to perform.
|
|
|
|
:param JID jid: Optionally provide specific JID.
|
|
|
|
:param string node: Optionally provide specific node.
|
|
|
|
"""
|
|
|
|
self.unregister(ctype, op, jid, node)
|
|
|
|
self.register(self._handler_defaults[ctype][op], ctype, op, jid, node)
|