From 3a12cdbd131e5bb98f192c077faa6bdda8fd95c7 Mon Sep 17 00:00:00 2001
From: Dann Martens <me@dannmartens.com>
Date: Thu, 13 Jan 2011 08:40:53 +0100
Subject: Introduced new XEP-0009 into develop.

---
 sleekxmpp/plugins/old_0009.py                 | 277 ++++++++++
 sleekxmpp/plugins/xep_0009.py                 | 277 ----------
 sleekxmpp/plugins/xep_0009/__init__.py        |  11 +
 sleekxmpp/plugins/xep_0009/binding.py         | 281 ++++++++++
 sleekxmpp/plugins/xep_0009/remote.py          | 752 ++++++++++++++++++++++++++
 sleekxmpp/plugins/xep_0009/rpc.py             | 221 ++++++++
 sleekxmpp/plugins/xep_0009/stanza/RPC.py      |  68 +++
 sleekxmpp/plugins/xep_0009/stanza/__init__.py |   9 +
 8 files changed, 1619 insertions(+), 277 deletions(-)
 create mode 100644 sleekxmpp/plugins/old_0009.py
 delete mode 100644 sleekxmpp/plugins/xep_0009.py
 create mode 100644 sleekxmpp/plugins/xep_0009/__init__.py
 create mode 100644 sleekxmpp/plugins/xep_0009/binding.py
 create mode 100644 sleekxmpp/plugins/xep_0009/remote.py
 create mode 100644 sleekxmpp/plugins/xep_0009/rpc.py
 create mode 100644 sleekxmpp/plugins/xep_0009/stanza/RPC.py
 create mode 100644 sleekxmpp/plugins/xep_0009/stanza/__init__.py

(limited to 'sleekxmpp')

diff --git a/sleekxmpp/plugins/old_0009.py b/sleekxmpp/plugins/old_0009.py
new file mode 100644
index 00000000..625b03fb
--- /dev/null
+++ b/sleekxmpp/plugins/old_0009.py
@@ -0,0 +1,277 @@
+"""
+XEP-0009 XMPP Remote Procedure Calls
+"""
+from __future__ import with_statement
+from . import base
+import logging
+from xml.etree import cElementTree as ET
+import copy
+import time
+import base64
+
+def py2xml(*args):
+	params = ET.Element("params")
+	for x in args:
+		param = ET.Element("param")
+		param.append(_py2xml(x))
+		params.append(param) #<params><param>...
+	return params
+
+def _py2xml(*args):
+	for x in args:
+		val = ET.Element("value")
+		if type(x) is int:
+			i4 = ET.Element("i4")
+			i4.text = str(x)
+			val.append(i4)
+		if type(x) is bool:
+			boolean = ET.Element("boolean")
+			boolean.text = str(int(x))
+			val.append(boolean)
+		elif type(x) is str:
+			string = ET.Element("string")
+			string.text = x
+			val.append(string)
+		elif type(x) is float:
+			double = ET.Element("double")
+			double.text = str(x)
+			val.append(double)
+		elif type(x) is rpcbase64:
+			b64 = ET.Element("Base64")
+			b64.text = x.encoded()
+			val.append(b64)
+		elif type(x) is rpctime:
+			iso = ET.Element("dateTime.iso8601")
+			iso.text = str(x)
+			val.append(iso)
+		elif type(x) is list:
+			array = ET.Element("array")
+			data = ET.Element("data")
+			for y in x:
+				data.append(_py2xml(y))
+			array.append(data)
+			val.append(array)
+		elif type(x) is dict:
+			struct = ET.Element("struct")
+			for y in x.keys():
+				member = ET.Element("member")
+				name = ET.Element("name")
+				name.text = y
+				member.append(name)
+				member.append(_py2xml(x[y]))
+				struct.append(member)
+			val.append(struct)
+		return val
+
+def xml2py(params):
+	vals = []
+	for param in params.findall('param'):
+		vals.append(_xml2py(param.find('value')))
+	return vals
+
+def _xml2py(value):
+	if value.find('i4') is not None:
+		return int(value.find('i4').text)
+	if value.find('int') is not None:
+		return int(value.find('int').text)
+	if value.find('boolean') is not None:
+		return bool(value.find('boolean').text)
+	if value.find('string') is not None:
+		return value.find('string').text
+	if value.find('double') is not None:
+		return float(value.find('double').text)
+	if value.find('Base64') is not None:
+		return rpcbase64(value.find('Base64').text)
+	if value.find('dateTime.iso8601') is not None:
+		return rpctime(value.find('dateTime.iso8601'))
+	if value.find('struct') is not None:
+		struct = {}
+		for member in value.find('struct').findall('member'):
+			struct[member.find('name').text] = _xml2py(member.find('value'))
+		return struct
+	if value.find('array') is not None:
+		array = []
+		for val in value.find('array').find('data').findall('value'):
+			array.append(_xml2py(val))
+		return array
+	raise ValueError()
+
+class rpcbase64(object):
+	def __init__(self, data):
+		#base 64 encoded string
+		self.data = data
+
+	def decode(self):
+		return base64.decodestring(data)
+
+	def __str__(self):
+		return self.decode()
+
+	def encoded(self):
+		return self.data
+
+class rpctime(object):
+	def __init__(self,data=None):
+		#assume string data is in iso format YYYYMMDDTHH:MM:SS
+		if type(data) is str:
+			self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
+		elif type(data) is time.struct_time:
+			self.timestamp = data
+		elif data is None:
+			self.timestamp = time.gmtime()
+		else:
+			raise ValueError()
+
+	def iso8601(self):
+		#return a iso8601 string
+		return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
+
+	def __str__(self):
+		return self.iso8601()
+
+class JabberRPCEntry(object):
+	def __init__(self,call):
+		self.call = call
+		self.result = None
+		self.error = None
+		self.allow = {} #{'<jid>':['<resource1>',...],...}
+		self.deny = {}
+
+	def check_acl(self, jid, resource):
+		#Check for deny
+		if jid in self.deny.keys():
+			if self.deny[jid] == None or resource in self.deny[jid]:
+				return False
+		#Check for allow
+		if allow == None:
+			return True
+		if jid in self.allow.keys():
+			if self.allow[jid] == None or resource in self.allow[jid]:
+				return True
+		return False
+
+	def acl_allow(self, jid, resource):
+		if jid == None:
+			self.allow = None
+		elif resource == None:
+			self.allow[jid] = None
+		elif jid in self.allow.keys():
+			self.allow[jid].append(resource)
+		else:
+			self.allow[jid] = [resource]
+		
+	def acl_deny(self, jid, resource):
+		if jid == None:
+			self.deny = None
+		elif resource == None:
+			self.deny[jid] = None
+		elif jid in self.deny.keys():
+			self.deny[jid].append(resource)
+		else:
+			self.deny[jid] = [resource]
+
+	def call_method(self, args):
+		ret = self.call(*args)
+
+class xep_0009(base.base_plugin):
+
+	def plugin_init(self):
+		self.xep = '0009'
+		self.description = 'Jabber-RPC'
+		self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", 
+                                      self._callMethod, name='Jabber RPC Call')
+		self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", 
+                                      self._callResult, name='Jabber RPC Result')
+		self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", 
+                                      self._callError, name='Jabber RPC Error')
+		self.entries = {}
+		self.activeCalls = []
+
+	def post_init(self):
+		base.base_plugin.post_init(self)
+		self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
+		self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc')
+
+	def register_call(self, method, name=None):
+		#@returns an string that can be used in acl commands.
+		with self.lock:
+			if name is None:
+				self.entries[method.__name__] = JabberRPCEntry(method)
+				return method.__name__
+			else:
+				self.entries[name] = JabberRPCEntry(method)
+				return name
+
+	def acl_allow(self, entry, jid=None, resource=None):
+		#allow the method entry to be called by the given jid and resource.
+		#if jid is None it will allow any jid/resource.
+		#if resource is None it will allow any resource belonging to the jid.
+		with self.lock:
+			if self.entries[entry]:
+				self.entries[entry].acl_allow(jid,resource)
+			else:
+				raise ValueError()
+	
+	def acl_deny(self, entry, jid=None, resource=None):
+		#Note: by default all requests are denied unless allowed with acl_allow.
+		#If you deny an entry it will not be allowed regardless of acl_allow
+		with self.lock:
+			if self.entries[entry]:
+				self.entries[entry].acl_deny(jid,resource)
+			else:
+				raise ValueError()
+	
+	def unregister_call(self, entry):
+		#removes the registered call
+		with self.lock:
+			if self.entries[entry]:
+				del self.entries[entry]
+			else:
+				raise ValueError()
+
+	def makeMethodCallQuery(self,pmethod,params):
+		query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
+		methodCall = ET.Element('methodCall')
+		methodName = ET.Element('methodName')
+		methodName.text = pmethod
+		methodCall.append(methodName)
+		methodCall.append(params)
+		query.append(methodCall)
+		return query
+ 
+	def makeIqMethodCall(self,pto,pmethod,params):
+		iq = self.xmpp.makeIqSet()
+		iq.set('to',pto)
+		iq.append(self.makeMethodCallQuery(pmethod,params))
+		return iq
+	
+	def makeIqMethodResponse(self,pto,pid,params):
+		iq = self.xmpp.makeIqResult(pid)
+		iq.set('to',pto)
+		query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
+		methodResponse = ET.Element('methodResponse')
+		methodResponse.append(params)
+		query.append(methodResponse)
+		return iq
+
+	def makeIqMethodError(self,pto,id,pmethod,params,condition):
+		iq = self.xmpp.makeIqError(id)
+		iq.set('to',pto)
+		iq.append(self.makeMethodCallQuery(pmethod,params))
+		iq.append(self.xmpp['xep_0086'].makeError(condition))
+		return iq
+	
+		
+
+	def call_remote(self, pto, pmethod, *args):
+		#calls a remote method. Returns the id of the Iq.
+		pass
+
+	def _callMethod(self,xml):
+		pass
+
+	def _callResult(self,xml):
+		pass
+
+	def _callError(self,xml):
+		pass
diff --git a/sleekxmpp/plugins/xep_0009.py b/sleekxmpp/plugins/xep_0009.py
deleted file mode 100644
index 625b03fb..00000000
--- a/sleekxmpp/plugins/xep_0009.py
+++ /dev/null
@@ -1,277 +0,0 @@
-"""
-XEP-0009 XMPP Remote Procedure Calls
-"""
-from __future__ import with_statement
-from . import base
-import logging
-from xml.etree import cElementTree as ET
-import copy
-import time
-import base64
-
-def py2xml(*args):
-	params = ET.Element("params")
-	for x in args:
-		param = ET.Element("param")
-		param.append(_py2xml(x))
-		params.append(param) #<params><param>...
-	return params
-
-def _py2xml(*args):
-	for x in args:
-		val = ET.Element("value")
-		if type(x) is int:
-			i4 = ET.Element("i4")
-			i4.text = str(x)
-			val.append(i4)
-		if type(x) is bool:
-			boolean = ET.Element("boolean")
-			boolean.text = str(int(x))
-			val.append(boolean)
-		elif type(x) is str:
-			string = ET.Element("string")
-			string.text = x
-			val.append(string)
-		elif type(x) is float:
-			double = ET.Element("double")
-			double.text = str(x)
-			val.append(double)
-		elif type(x) is rpcbase64:
-			b64 = ET.Element("Base64")
-			b64.text = x.encoded()
-			val.append(b64)
-		elif type(x) is rpctime:
-			iso = ET.Element("dateTime.iso8601")
-			iso.text = str(x)
-			val.append(iso)
-		elif type(x) is list:
-			array = ET.Element("array")
-			data = ET.Element("data")
-			for y in x:
-				data.append(_py2xml(y))
-			array.append(data)
-			val.append(array)
-		elif type(x) is dict:
-			struct = ET.Element("struct")
-			for y in x.keys():
-				member = ET.Element("member")
-				name = ET.Element("name")
-				name.text = y
-				member.append(name)
-				member.append(_py2xml(x[y]))
-				struct.append(member)
-			val.append(struct)
-		return val
-
-def xml2py(params):
-	vals = []
-	for param in params.findall('param'):
-		vals.append(_xml2py(param.find('value')))
-	return vals
-
-def _xml2py(value):
-	if value.find('i4') is not None:
-		return int(value.find('i4').text)
-	if value.find('int') is not None:
-		return int(value.find('int').text)
-	if value.find('boolean') is not None:
-		return bool(value.find('boolean').text)
-	if value.find('string') is not None:
-		return value.find('string').text
-	if value.find('double') is not None:
-		return float(value.find('double').text)
-	if value.find('Base64') is not None:
-		return rpcbase64(value.find('Base64').text)
-	if value.find('dateTime.iso8601') is not None:
-		return rpctime(value.find('dateTime.iso8601'))
-	if value.find('struct') is not None:
-		struct = {}
-		for member in value.find('struct').findall('member'):
-			struct[member.find('name').text] = _xml2py(member.find('value'))
-		return struct
-	if value.find('array') is not None:
-		array = []
-		for val in value.find('array').find('data').findall('value'):
-			array.append(_xml2py(val))
-		return array
-	raise ValueError()
-
-class rpcbase64(object):
-	def __init__(self, data):
-		#base 64 encoded string
-		self.data = data
-
-	def decode(self):
-		return base64.decodestring(data)
-
-	def __str__(self):
-		return self.decode()
-
-	def encoded(self):
-		return self.data
-
-class rpctime(object):
-	def __init__(self,data=None):
-		#assume string data is in iso format YYYYMMDDTHH:MM:SS
-		if type(data) is str:
-			self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
-		elif type(data) is time.struct_time:
-			self.timestamp = data
-		elif data is None:
-			self.timestamp = time.gmtime()
-		else:
-			raise ValueError()
-
-	def iso8601(self):
-		#return a iso8601 string
-		return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
-
-	def __str__(self):
-		return self.iso8601()
-
-class JabberRPCEntry(object):
-	def __init__(self,call):
-		self.call = call
-		self.result = None
-		self.error = None
-		self.allow = {} #{'<jid>':['<resource1>',...],...}
-		self.deny = {}
-
-	def check_acl(self, jid, resource):
-		#Check for deny
-		if jid in self.deny.keys():
-			if self.deny[jid] == None or resource in self.deny[jid]:
-				return False
-		#Check for allow
-		if allow == None:
-			return True
-		if jid in self.allow.keys():
-			if self.allow[jid] == None or resource in self.allow[jid]:
-				return True
-		return False
-
-	def acl_allow(self, jid, resource):
-		if jid == None:
-			self.allow = None
-		elif resource == None:
-			self.allow[jid] = None
-		elif jid in self.allow.keys():
-			self.allow[jid].append(resource)
-		else:
-			self.allow[jid] = [resource]
-		
-	def acl_deny(self, jid, resource):
-		if jid == None:
-			self.deny = None
-		elif resource == None:
-			self.deny[jid] = None
-		elif jid in self.deny.keys():
-			self.deny[jid].append(resource)
-		else:
-			self.deny[jid] = [resource]
-
-	def call_method(self, args):
-		ret = self.call(*args)
-
-class xep_0009(base.base_plugin):
-
-	def plugin_init(self):
-		self.xep = '0009'
-		self.description = 'Jabber-RPC'
-		self.xmpp.add_handler("<iq type='set'><query xmlns='jabber:iq:rpc' /></iq>", 
-                                      self._callMethod, name='Jabber RPC Call')
-		self.xmpp.add_handler("<iq type='result'><query xmlns='jabber:iq:rpc' /></iq>", 
-                                      self._callResult, name='Jabber RPC Result')
-		self.xmpp.add_handler("<iq type='error'><query xmlns='jabber:iq:rpc' /></iq>", 
-                                      self._callError, name='Jabber RPC Error')
-		self.entries = {}
-		self.activeCalls = []
-
-	def post_init(self):
-		base.base_plugin.post_init(self)
-		self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
-		self.xmpp.plugin['xep_0030'].add_identity('automatition','rpc')
-
-	def register_call(self, method, name=None):
-		#@returns an string that can be used in acl commands.
-		with self.lock:
-			if name is None:
-				self.entries[method.__name__] = JabberRPCEntry(method)
-				return method.__name__
-			else:
-				self.entries[name] = JabberRPCEntry(method)
-				return name
-
-	def acl_allow(self, entry, jid=None, resource=None):
-		#allow the method entry to be called by the given jid and resource.
-		#if jid is None it will allow any jid/resource.
-		#if resource is None it will allow any resource belonging to the jid.
-		with self.lock:
-			if self.entries[entry]:
-				self.entries[entry].acl_allow(jid,resource)
-			else:
-				raise ValueError()
-	
-	def acl_deny(self, entry, jid=None, resource=None):
-		#Note: by default all requests are denied unless allowed with acl_allow.
-		#If you deny an entry it will not be allowed regardless of acl_allow
-		with self.lock:
-			if self.entries[entry]:
-				self.entries[entry].acl_deny(jid,resource)
-			else:
-				raise ValueError()
-	
-	def unregister_call(self, entry):
-		#removes the registered call
-		with self.lock:
-			if self.entries[entry]:
-				del self.entries[entry]
-			else:
-				raise ValueError()
-
-	def makeMethodCallQuery(self,pmethod,params):
-		query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
-		methodCall = ET.Element('methodCall')
-		methodName = ET.Element('methodName')
-		methodName.text = pmethod
-		methodCall.append(methodName)
-		methodCall.append(params)
-		query.append(methodCall)
-		return query
- 
-	def makeIqMethodCall(self,pto,pmethod,params):
-		iq = self.xmpp.makeIqSet()
-		iq.set('to',pto)
-		iq.append(self.makeMethodCallQuery(pmethod,params))
-		return iq
-	
-	def makeIqMethodResponse(self,pto,pid,params):
-		iq = self.xmpp.makeIqResult(pid)
-		iq.set('to',pto)
-		query = self.xmpp.makeIqQuery(iq,"jabber:iq:rpc")
-		methodResponse = ET.Element('methodResponse')
-		methodResponse.append(params)
-		query.append(methodResponse)
-		return iq
-
-	def makeIqMethodError(self,pto,id,pmethod,params,condition):
-		iq = self.xmpp.makeIqError(id)
-		iq.set('to',pto)
-		iq.append(self.makeMethodCallQuery(pmethod,params))
-		iq.append(self.xmpp['xep_0086'].makeError(condition))
-		return iq
-	
-		
-
-	def call_remote(self, pto, pmethod, *args):
-		#calls a remote method. Returns the id of the Iq.
-		pass
-
-	def _callMethod(self,xml):
-		pass
-
-	def _callResult(self,xml):
-		pass
-
-	def _callError(self,xml):
-		pass
diff --git a/sleekxmpp/plugins/xep_0009/__init__.py b/sleekxmpp/plugins/xep_0009/__init__.py
new file mode 100644
index 00000000..2cd14170
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0009/__init__.py
@@ -0,0 +1,11 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.plugins.xep_0009 import stanza
+from sleekxmpp.plugins.xep_0009.rpc import xep_0009
+from sleekxmpp.plugins.xep_0009.stanza import RPCQuery, MethodCall, MethodResponse
diff --git a/sleekxmpp/plugins/xep_0009/binding.py b/sleekxmpp/plugins/xep_0009/binding.py
new file mode 100644
index 00000000..6b50d99e
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0009/binding.py
@@ -0,0 +1,281 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from xml.etree import cElementTree as ET
+import base64
+import logging
+import time
+
+
+
+log = logging.getLogger(__name__)
+
+_namespace = 'jabber:iq:rpc'  
+
+def fault2xml(fault):
+    value = dict()
+    value['faultCode'] = fault['code']
+    value['faultString'] = fault['string']
+    fault = ET.Element("fault", {'xmlns': _namespace})
+    fault.append(_py2xml((value)))
+    return fault
+
+def xml2fault(params):  
+    vals = []
+    for value in params.findall('{%s}value' % _namespace):
+        vals.append(_xml2py(value))
+    fault = dict()
+    fault['code'] = vals[0]['faultCode']
+    fault['string'] = vals[0]['faultString']
+    return fault
+
+def py2xml(*args):
+    params = ET.Element("{%s}params" % _namespace)
+    for x in args:
+        param = ET.Element("param")
+        param.append(_py2xml(x))
+        params.append(param) #<params><param>...
+    return params
+
+def _py2xml(*args):
+    for x in args:
+        val = ET.Element("value")
+        if x is None:
+            nil = ET.Element("nil")
+            val.append(nil)
+        elif type(x) is int:
+            i4 = ET.Element("i4")
+            i4.text = str(x)
+            val.append(i4)
+        elif type(x) is bool:
+            boolean = ET.Element("boolean")
+            boolean.text = str(int(x))
+            val.append(boolean)
+        elif type(x) is str:
+            string = ET.Element("string")
+            string.text = x
+            val.append(string)
+        elif type(x) is float:
+            double = ET.Element("double")
+            double.text = str(x)
+            val.append(double)
+        elif type(x) is rpcbase64:
+            b64 = ET.Element("Base64")
+            b64.text = x.encoded()
+            val.append(b64)
+        elif type(x) is rpctime:
+            iso = ET.Element("dateTime.iso8601")
+            iso.text = str(x)
+            val.append(iso)
+        elif type(x) in (list, tuple):
+            array = ET.Element("array")
+            data = ET.Element("data")
+            for y in x:
+                data.append(_py2xml(y))
+            array.append(data)
+            val.append(array)
+        elif type(x) is dict:
+            struct = ET.Element("struct")
+            for y in x.keys():
+                member = ET.Element("member")
+                name = ET.Element("name")
+                name.text = y
+                member.append(name)
+                member.append(_py2xml(x[y]))
+                struct.append(member)
+            val.append(struct)
+        return val
+
+#def py2xml(*args):
+#    params = ET.Element("{%s}params" % _namespace)
+#    for x in args:
+#        param = ET.Element("{%s}param" % _namespace)
+#        param.append(_py2xml(x))
+#        params.append(param) #<params><param>...
+#    return params
+#
+#def _py2xml(*args):
+#    for x in args:
+#        val = ET.Element("{%s}value" % _namespace)
+#        if x is None:
+#            nil = ET.Element("{%s}nil" % _namespace)
+#            val.append(nil)
+#        elif type(x) is int:
+#            i4 = ET.Element("{%s}i4" % _namespace)
+#            i4.text = str(x)
+#            val.append(i4)
+#        elif type(x) is bool:
+#            boolean = ET.Element("{%s}boolean" % _namespace)
+#            boolean.text = str(int(x))
+#            val.append(boolean)
+#        elif type(x) is str:
+#            string = ET.Element("{%s}string" % _namespace)
+#            string.text = x
+#            val.append(string)
+#        elif type(x) is float:
+#            double = ET.Element("{%s}double" % _namespace)
+#            double.text = str(x)
+#            val.append(double)
+#        elif type(x) is rpcbase64:
+#            b64 = ET.Element("{%s}Base64" % _namespace)
+#            b64.text = x.encoded()
+#            val.append(b64)
+#        elif type(x) is rpctime:
+#            iso = ET.Element("{%s}dateTime.iso8601" % _namespace)
+#            iso.text = str(x)
+#            val.append(iso)
+#        elif type(x) in (list, tuple):
+#            array = ET.Element("{%s}array" % _namespace)
+#            data = ET.Element("{%s}data" % _namespace)
+#            for y in x:
+#                data.append(_py2xml(y))
+#            array.append(data)
+#            val.append(array)
+#        elif type(x) is dict:
+#            struct = ET.Element("{%s}struct" % _namespace)
+#            for y in x.keys():
+#                member = ET.Element("{%s}member" % _namespace)
+#                name = ET.Element("{%s}name" % _namespace)
+#                name.text = y
+#                member.append(name)
+#                member.append(_py2xml(x[y]))
+#                struct.append(member)
+#            val.append(struct)
+#        return val
+    
+
+#def py2xml(*args):
+#    params = ET.Element("params", {'xmlns': _namespace})
+#    for x in args:
+#        param = ET.Element("param", {'xmlns': _namespace})
+#        param.append(_py2xml(x))
+#        params.append(param) #<params><param>...
+#    return params
+#
+#def _py2xml(*args):
+#    for x in args:
+#        val = ET.Element("value", {'xmlns': _namespace})
+#        if x is None:
+#            nil = ET.Element("nil", {'xmlns': _namespace})
+#            val.append(nil)
+#        elif type(x) is int:
+#            i4 = ET.Element("i4", {'xmlns': _namespace})
+#            i4.text = str(x)
+#            val.append(i4)
+#        elif type(x) is bool:
+#            boolean = ET.Element("boolean", {'xmlns': _namespace})
+#            boolean.text = str(int(x))
+#            val.append(boolean)
+#        elif type(x) is str:
+#            string = ET.Element("string", {'xmlns': _namespace})
+#            string.text = x
+#            val.append(string)
+#        elif type(x) is float:
+#            double = ET.Element("double", {'xmlns': _namespace})
+#            double.text = str(x)
+#            val.append(double)
+#        elif type(x) is rpcbase64:
+#            b64 = ET.Element("Base64", {'xmlns': _namespace})
+#            b64.text = x.encoded()
+#            val.append(b64)
+#        elif type(x) is rpctime:
+#            iso = ET.Element("dateTime.iso8601", {'xmlns': _namespace})
+#            iso.text = str(x)
+#            val.append(iso)
+#        elif type(x) in (list, tuple):
+#            array = ET.Element("array", {'xmlns': _namespace})
+#            data = ET.Element("data", {'xmlns': _namespace})
+#            for y in x:
+#                data.append(_py2xml(y))
+#            array.append(data)
+#            val.append(array)
+#        elif type(x) is dict:
+#            struct = ET.Element("struct", {'xmlns': _namespace})
+#            for y in x.keys():
+#                member = ET.Element("member", {'xmlns': _namespace})
+#                name = ET.Element("name", {'xmlns': _namespace})
+#                name.text = y
+#                member.append(name)
+#                member.append(_py2xml(x[y]))
+#                struct.append(member)
+#            val.append(struct)
+#        return val
+
+def xml2py(params):
+    namespace = 'jabber:iq:rpc'
+    vals = []
+    for param in params.findall('{%s}param' % namespace):
+        vals.append(_xml2py(param.find('{%s}value' % namespace)))
+    return vals
+
+def _xml2py(value):
+    namespace = 'jabber:iq:rpc'
+    if value.find('{%s}i4' % namespace) is not None:
+        return int(value.find('{%s}i4' % namespace).text)
+    if value.find('{%s}int' % namespace) is not None:
+        return int(value.find('{%s}int' % namespace).text)
+    if value.find('{%s}boolean' % namespace) is not None:
+        return bool(value.find('{%s}boolean' % namespace).text)
+    if value.find('{%s}string' % namespace) is not None:
+        return value.find('{%s}string' % namespace).text
+    if value.find('{%s}double' % namespace) is not None:
+        return float(value.find('{%s}double' % namespace).text)
+    if value.find('{%s}Base64') is not None:
+        return rpcbase64(value.find('Base64' % namespace).text)
+    if value.find('{%s}dateTime.iso8601') is not None:
+        return rpctime(value.find('{%s}dateTime.iso8601'))
+    if value.find('{%s}struct' % namespace) is not None:
+        struct = {}
+        for member in value.find('{%s}struct' % namespace).findall('{%s}member' % namespace):
+            struct[member.find('{%s}name' % namespace).text] = _xml2py(member.find('{%s}value' % namespace))
+        return struct
+    if value.find('{%s}array' % namespace) is not None:
+        array = []
+        for val in value.find('{%s}array' % namespace).find('{%s}data' % namespace).findall('{%s}value' % namespace):
+            array.append(_xml2py(val))
+        return array
+    raise ValueError()
+
+
+
+class rpcbase64(object):
+    
+    def __init__(self, data):
+        #base 64 encoded string
+        self.data = data
+
+    def decode(self):
+        return base64.decodestring(self.data)
+
+    def __str__(self):
+        return self.decode()
+
+    def encoded(self):
+        return self.data
+
+
+
+class rpctime(object):
+    
+    def __init__(self,data=None):
+        #assume string data is in iso format YYYYMMDDTHH:MM:SS
+        if type(data) is str:
+            self.timestamp = time.strptime(data,"%Y%m%dT%H:%M:%S")
+        elif type(data) is time.struct_time:
+            self.timestamp = data
+        elif data is None:
+            self.timestamp = time.gmtime()
+        else:
+            raise ValueError()
+
+    def iso8601(self):
+        #return a iso8601 string
+        return time.strftime("%Y%m%dT%H:%M:%S",self.timestamp)
+
+    def __str__(self):
+        return self.iso8601()
diff --git a/sleekxmpp/plugins/xep_0009/remote.py b/sleekxmpp/plugins/xep_0009/remote.py
new file mode 100644
index 00000000..bd931c0c
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0009/remote.py
@@ -0,0 +1,752 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from binding import py2xml, xml2py, xml2fault, fault2xml
+from threading import RLock
+import abc
+import inspect
+import logging
+import sleekxmpp
+import sys
+import threading
+import traceback
+
+
+
+log = logging.getLogger(__name__)
+
+
+
+def _intercept(method, name, public):
+    def _resolver(instance, *args, **kwargs):
+        log.debug("Locally calling %s.%s with arguments %s." % (instance.FQN(), method.__name__, args))
+        try: 
+            value = method(instance, *args, **kwargs)
+            if value == NotImplemented:
+                raise InvocationException("Local handler does not implement %s.%s!" % (instance.FQN(), method.__name__))            
+            return value
+        except InvocationException:
+            raise
+        except Exception as e:
+            raise InvocationException("A problem occured calling %s.%s!" % (instance.FQN(), method.__name__), e)
+    _resolver._rpc = public
+    _resolver._rpc_name = method.__name__ if name is None else name
+    return _resolver
+
+def remote(function_argument, public = True):
+    '''
+    Decorator for methods which are remotely callable. This decorator
+    works in conjunction with classes which extend ABC Endpoint.
+    Example:
+    
+        @remote
+        def remote_method(arg1, arg2)
+    
+    Arguments:
+        function_argument -- a stand-in for either the actual method
+            OR a new name (string) for the method. In that case the
+            method is considered mapped:
+            Example:
+            
+            @remote("new_name")
+            def remote_method(arg1, arg2)
+            
+        public -- A flag which indicates if this method should be part
+            of the known dictionary of remote methods. Defaults to True.
+            Example:
+            
+            @remote(False)
+            def remote_method(arg1, arg2)
+            
+    Note: renaming and revising (public vs. private) can be combined.
+    Example:
+    
+            @remote("new_name", False)
+            def remote_method(arg1, arg2)
+    '''
+    if hasattr(function_argument, '__call__'):
+        return _intercept(function_argument, None, public)
+    else:        
+        if not isinstance(function_argument, basestring):
+            if not isinstance(function_argument, bool):                
+                raise Exception('Expected an RPC method name or visibility modifier!')
+            else:
+                def _wrap_revised(function):
+                    function = _intercept(function, None, function_argument)
+                    return function
+                return _wrap_revised                
+        def _wrap_remapped(function):
+            function = _intercept(function, function_argument, public)
+            return function
+        return _wrap_remapped
+    
+    
+    
+class ACL:
+    '''
+    An Access Control List (ACL) is a list of rules, which are evaluated
+    in order until a match is found. The policy of the matching rule
+    is then applied.
+
+    Rules are 3-tuples, consisting of a policy enumerated type, a JID
+    expression and a RCP resource expression.
+
+    Examples:
+    [ (ACL.ALLOW, '*', '*') ] allow everyone everything, no restrictions
+    [ (ACL.DENY, '*', '*') ] deny everyone everything, no restrictions
+    [ (ACL.ALLOW, 'test@xmpp.org/unit', 'test.*'),
+      (ACL.DENY, '*', '*') ] deny everyone everything, except named
+        JID, which is allowed access to endpoint 'test' only.
+    
+    The use of wildcards is allowed in expressions, as follows:
+    '*' everyone, or everything (= all endpoints and methods)
+    'test@xmpp.org/*' every JID regardless of JID resource
+    '*@xmpp.org/rpc' every JID from domain xmpp.org with JID res 'rpc'
+    'frank@*' every 'frank', regardless of domain or JID res
+    'system.*' all methods of endpoint 'system'
+    '*.reboot' all methods reboot regardless of endpoint
+    '''
+    ALLOW = True
+    DENY = False
+    
+    @classmethod
+    def check(cls, rules, jid, resource):
+        if rules is None:
+            return cls.DENY                  # No rules means no access!
+        for rule in rules:
+            policy = cls._check(rule, jid, resource)
+            if policy is not None:
+                return policy        
+        return cls.DENY   # By default if not rule matches, deny access.
+    
+    @classmethod
+    def _check(cls, rule, jid, resource):
+        if cls._match(jid, rule[1]) and cls._match(resource, rule[2]):
+            return rule[0]
+        else:
+            return None
+
+    @classmethod
+    def _next_token(cls, expression, index):
+        new_index = expression.find('*', index)
+        if new_index == 0:
+            return ''
+        else:
+            if new_index == -1:
+                return expression[index : ]                
+            else:
+                return expression[index : new_index]
+
+    @classmethod
+    def _match(cls, value, expression):  
+        #! print "_match [VALUE] %s [EXPR] %s" % (value, expression)      
+        index = 0
+        position = 0
+        while index < len(expression):
+            token = cls._next_token(expression, index)
+            #! print "[TOKEN] '%s'" % token
+            size = len(token)
+            if size > 0:
+                token_index = value.find(token, position)
+                if token_index == -1:
+                    return False
+                else:
+                    #! print "[INDEX-OF] %s" % token_index
+                    position = token_index + len(token)
+                pass
+            if size == 0:
+                index += 1
+            else:
+                index += size
+        #! print "index %s position %s" % (index, position)
+        return True
+
+ANY_ALL = [ (ACL.ALLOW, '*', '*') ]
+
+
+
+class RemoteException(Exception):
+    '''
+    Base exception for RPC. This exception is raised when a problem
+    occurs in the network layer.
+    '''
+    
+    def __init__(self, message="", cause=None):
+        '''
+        Initializes a new RemoteException.
+        
+        Arguments:
+            message -- The message accompanying this exception.
+            cause -- The underlying cause of this exception.
+        '''        
+        self._message = message
+        self._cause = cause  
+        pass 
+
+    def __str__(self):
+        return repr(self._message)
+
+    def get_message(self):
+        return self._message
+
+    def get_cause(self):
+        return self._cause
+
+
+
+class InvocationException(RemoteException):
+    '''
+    Exception raised when a problem occurs during the remote invocation
+    of a method. 
+    '''
+    pass
+
+
+
+class AuthorizationException(RemoteException):
+    '''
+    Exception raised when the caller is not authorized to invoke the
+    remote method.
+    '''
+    pass
+
+
+
+class TimeoutException(Exception):
+    '''
+    Exception raised when the synchronous execution of a method takes
+    longer than the given threshold because an underlying asynchronous
+    reply did not arrive in time.
+    '''
+    pass 
+
+
+
+class Callback(object):
+    '''
+    A base class for callback handlers. 
+    '''
+    __metaclass__ = abc.ABCMeta
+    
+    
+    @abc.abstractproperty
+    def set_value(self, value):
+        return NotImplemented
+    
+    @abc.abstractproperty
+    def cancel_with_error(self, exception):
+        return NotImplemented
+
+
+
+class Future(Callback):
+    '''
+    Represents the result of an asynchronous computation.
+    '''
+    
+    def __init__(self):
+        '''
+        Initializes a new Future.
+        '''
+        self._value = None
+        self._exception = None        
+        self._event = threading.Event()
+        pass
+    
+    def set_value(self, value):
+        '''
+        Sets the value of this Future. Once the value is set, a caller
+        blocked on get_value will be able to continue.
+        '''
+        self._value = value
+        self._event.set()
+        
+    def get_value(self, timeout=None):
+        '''
+        Gets the value of this Future. This call will block until
+        the result is available, or until an optional timeout expires.
+        When this Future is cancelled with an error,  
+        
+        Arguments:
+            timeout -- The maximum waiting time to obtain the value.
+        '''
+        self._event.wait(timeout)
+        if self._exception:
+            raise self._exception
+        if not self._event.is_set():
+            raise TimeoutException
+        return self._value
+    
+    def is_done(self):
+        '''
+        Returns true if a value has been returned.
+        '''
+        return self._event.is_set()
+
+    def cancel_with_error(self, exception):
+        '''
+        Cancels the Future because of an error. Once cancelled, a 
+        caller blocked on get_value will be able to continue.
+        '''
+        self._exception = exception     
+        self._event.set()      
+
+
+        
+class Endpoint(object):
+    '''
+    The Endpoint class is an abstract base class for all objects
+    participating in an RPC-enabled XMPP network.
+    
+    A user subclassing this class is required to implement the method:
+        FQN(self) 
+    where FQN stands for Fully Qualified Name, an unambiguous name 
+    which specifies which object an RPC call refers to. It is the 
+    first part in a RPC method name '<fqn>.<method>'.
+    '''
+    __metaclass__ = abc.ABCMeta
+
+
+    def __init__(self, session, target_jid):
+        '''
+        Initialize a new Endpoint. This constructor should never be
+        invoked by a user, instead it will be called by the factories
+        which instantiate the RPC-enabled objects, of which only
+        the classes are provided by the user. 
+        
+        Arguments:
+            session -- An RPC session instance.
+            target_jid -- the identity of the remote XMPP entity.
+        '''
+        self.session = session
+        self.target_jid = target_jid
+        
+    @abc.abstractproperty
+    def FQN(self):
+        return NotImplemented
+    
+    def get_methods(self):
+        '''
+        Returns a dictionary of all RPC method names provided by this
+        class. This method returns the actual  method names as found
+        in the class definition which have been decorated with:
+        
+            @remote
+            def some_rpc_method(arg1, arg2)
+        
+        
+        Unless:
+            (1) the name has been remapped, in which case the new
+                name will be returned.
+                
+                    @remote("new_name")
+                    def some_rpc_method(arg1, arg2)
+                
+            (2) the method is set to hidden
+            
+                    @remote(False)
+                    def some_hidden_method(arg1, arg2)
+        '''
+        result = dict()
+        for function in dir(self):
+            test_attr = getattr(self, function, None)
+            try:
+                if test_attr._rpc:
+                    result[test_attr._rpc_name] = test_attr
+            except Exception:
+                pass
+        return result     
+
+
+
+class Proxy(Endpoint):
+    '''
+    Implementation of the Proxy pattern which is intended to wrap
+    around Endpoints in order to intercept calls, marshall them and
+    forward them to the remote object.
+    '''
+
+    def __init__(self, endpoint, callback = None):
+        '''
+        Initializes a new Proxy.
+        
+        Arguments:
+            endpoint -- The endpoint which is proxified.
+        '''
+        self._endpoint = endpoint
+        self._callback = callback
+        
+    def __getattribute__(self, name, *args):
+        if name in ('__dict__', '_endpoint', 'async', '_callback'):
+            return object.__getattribute__(self, name)
+        else:
+            attribute = self._endpoint.__getattribute__(name)
+            if hasattr(attribute, '__call__'):
+                try:
+                    if attribute._rpc:
+                        def _remote_call(*args, **kwargs): 
+                            log.debug("Remotely calling '%s.%s' with arguments %s." % (self._endpoint.FQN(), attribute._rpc_name, args))
+                            return self._endpoint.session._call_remote(self._endpoint.target_jid, "%s.%s" % (self._endpoint.FQN(), attribute._rpc_name), self._callback, *args, **kwargs)
+                        return _remote_call
+                except:
+                    pass   # If the attribute doesn't exist, don't care! 
+            return attribute
+    
+    def async(self, callback):        
+        return Proxy(self._endpoint, callback)
+               
+    def get_endpoint(self):
+        '''
+        Returns the proxified endpoint.
+        '''
+        return self._endpoint
+        
+    def FQN(self):
+        return self._endpoint.FQN()
+
+
+
+class JabberRPCEntry(object):
+    
+    
+    def __init__(self, endpoint_FQN, call):
+        self._endpoint_FQN = endpoint_FQN
+        self._call = call
+
+    def call_method(self, args):
+        return_value = self._call(*args)
+        if return_value is None:
+            return return_value
+        else:
+            return self._return(return_value)
+        
+    def get_endpoint_FQN(self):
+        return self._endpoint_FQN
+
+    def _return(self, *args):
+        return args
+    
+    
+    
+class RemoteSession(object):
+    '''
+    A context object for a Jabber-RPC session. 
+    '''
+    
+            
+    def __init__(self, client, session_close_callback):
+        '''
+        Initializes a new RPC session.
+        
+        Arguments:
+            client -- The SleekXMPP client associated with this session.
+            session_close_callback -- A callback called when the 
+                session is closed.
+        '''
+        self._client = client
+        self._session_close_callback = session_close_callback
+        self._event = threading.Event()
+        self._entries = {}
+        self._callbacks = {}
+        self._acls = {}
+        self._lock = RLock()
+            
+    def _wait(self):
+        self._event.wait()
+    
+    def _notify(self, event):
+        log.debug("RPC Session as %s started." % self._client.boundjid.full)
+        self._client.sendPresence()
+        self._event.set()
+        pass
+        
+    def _register_call(self, endpoint, method, name=None):
+        '''
+        Registers a method from an endpoint as remotely callable.
+        '''
+        if name is None:
+            name = method.__name__
+        key = "%s.%s" % (endpoint, name)
+        log.debug("Registering call handler for %s (%s)." % (key, method))
+        with self._lock:
+            if self._entries.has_key(key):
+                raise KeyError("A handler for %s has already been regisered!" % endpoint)
+            self._entries[key] = JabberRPCEntry(endpoint, method)
+        return key
+
+    def _register_acl(self, endpoint, acl):
+        log.debug("Registering ACL %s for endpoint %s." % (repr(acl), endpoint))
+        with self._lock:
+            self._acls[endpoint] = acl
+
+    def _register_callback(self, pid, callback):
+        with self._lock:
+            self._callbacks[pid] = callback
+            
+    def forget_callback(self, callback):        
+        with self._lock:
+            pid = self._find_key(self._callbacks, callback)
+            if pid is not None:
+                del self._callback[pid]
+            else:
+                raise ValueError("Unknown callback!")
+        pass
+    
+    def _find_key(self, dict, value):
+        """return the key of dictionary dic given the value"""
+        search = [k for k, v in dict.iteritems() if v == value]
+        if len(search) == 0:
+            return None
+        else:
+            return search[0]
+    
+    def _unregister_call(self, key):
+        #removes the registered call
+        with self._lock:
+            if self._entries[key]:
+                del self._entries[key]
+            else:
+                raise ValueError()
+        
+    def new_proxy(self, target_jid, endpoint_cls):
+        '''
+        Instantiates a new proxy object, which proxies to a remote
+        endpoint. This method uses a class reference without 
+        constructor arguments to instantiate the proxy.
+        
+        Arguments:
+            target_jid -- the XMPP entity ID hosting the endpoint.
+            endpoint_cls -- The remote (duck) type.
+        '''
+        try:            
+            argspec = inspect.getargspec(endpoint_cls.__init__)
+            args = [None] * (len(argspec[0]) - 1)
+            result = endpoint_cls(*args)
+            Endpoint.__init__(result, self, target_jid)
+            return Proxy(result)
+        except:
+            traceback.print_exc(file=sys.stdout)
+    
+    def new_handler(self, acl, handler_cls, *args, **kwargs):
+        '''
+        Instantiates a new handler object, which is called remotely
+        by others. The user can control the effect of the call by
+        implementing the remote method in the local endpoint class. The
+        returned reference can be called locally and will behave as a
+        regular instance.
+        
+        Arguments:
+            acl -- Access control list (see ACL class)
+            handler_clss -- The local (duck) type.
+            *args -- Constructor arguments for the local type.
+            **kwargs -- Constructor keyworded arguments for the local
+                type.
+        '''
+        argspec = inspect.getargspec(handler_cls.__init__)
+        base_argspec = inspect.getargspec(Endpoint.__init__)
+        if(argspec == base_argspec):
+            result = handler_cls(self, self._client.boundjid.full)
+        else:
+            result = handler_cls(*args, **kwargs)
+            Endpoint.__init__(result, self, self._client.boundjid.full)
+        method_dict = result.get_methods()
+        for method_name, method in method_dict.iteritems():
+            #!!! self._client.plugin['xep_0009'].register_call(result.FQN(), method, method_name)        
+            self._register_call(result.FQN(), method, method_name)
+        self._register_acl(result.FQN(), acl)
+        return result
+                         
+#    def is_available(self, targetCls, pto):
+#        return self._client.is_available(pto)
+
+    def _call_remote(self, pto, pmethod, callback, *arguments):
+        iq = self._client.plugin['xep_0009'].make_iq_method_call(pto, pmethod, py2xml(*arguments))
+        pid = iq['id']
+        if callback is None:
+            future = Future()
+            self._register_callback(pid, future)
+            iq.send()
+            return future.get_value(30)            
+        else:
+            print "[RemoteSession] _call_remote %s" % callback
+            self._register_callback(pid, callback)
+            iq.send()
+        
+    def close(self):
+        '''
+        Closes this session.
+        '''
+        self._client.disconnect(False)
+        self._session_close_callback()
+                      
+    def _on_jabber_rpc_method_call(self, iq):
+        iq.enable('rpc_query')
+        params = iq['rpc_query']['method_call']['params']
+        args = xml2py(params)
+        pmethod = iq['rpc_query']['method_call']['method_name']
+        try:
+            with self._lock:
+                entry = self._entries[pmethod]
+                rules = self._acls[entry.get_endpoint_FQN()]
+            if ACL.check(rules, iq['from'], pmethod):
+                return_value = entry.call_method(args)
+            else:
+                raise AuthorizationException("Unauthorized access to %s from %s!" % (pmethod, iq['from']))
+            if return_value is None:
+                return_value = ()
+            response = self._client.plugin['xep_0009'].make_iq_method_response(iq['id'], iq['from'], py2xml(*return_value))
+            response.send()
+        except InvocationException as ie:
+            fault = dict()
+            fault['code'] = 500
+            fault['string'] = ie.get_message()
+            self._client.plugin['xep_0009']._send_fault(iq, fault2xml(fault))
+        except AuthorizationException as ae:
+            log.error(ae.get_message())
+            error = self._client.plugin['xep_0009']._forbidden(iq)
+            error.send()                                            
+        except Exception as e:            
+            if isinstance(e, KeyError):
+                log.error("No handler available for %s!" % pmethod)
+                error = self._client.plugin['xep_0009']._item_not_found(iq)
+            else:
+                traceback.print_exc(file=sys.stderr)
+                log.error("An unexpected problem occurred invoking method %s!" % pmethod)
+                error = self._client.plugin['xep_0009']._undefined_condition(iq) 
+            #! print "[REMOTE.PY] _handle_remote_procedure_call AN ERROR SHOULD BE SENT NOW %s " % e
+            error.send()
+
+    def _on_jabber_rpc_method_response(self, iq):
+        iq.enable('rpc_query')
+        args = xml2py(iq['rpc_query']['method_response']['params'])
+        pid = iq['id']
+        with self._lock:
+            callback = self._callbacks[pid]
+            del self._callbacks[pid]
+        if(len(args) > 0):
+            callback.set_value(args[0])
+        else:
+            callback.set_value(None)
+        pass        
+
+    def _on_jabber_rpc_method_response2(self, iq):
+        iq.enable('rpc_query')
+        if iq['rpc_query']['method_response']['fault'] is not None:
+            self._on_jabber_rpc_method_fault(iq)
+        else:
+            args = xml2py(iq['rpc_query']['method_response']['params'])
+            pid = iq['id']
+            with self._lock:
+                callback = self._callbacks[pid]
+                del self._callbacks[pid]
+            if(len(args) > 0):
+                callback.set_value(args[0])
+            else:
+                callback.set_value(None)
+            pass    
+
+    def _on_jabber_rpc_method_fault(self, iq):
+        iq.enable('rpc_query')
+        fault = xml2fault(iq['rpc_query']['method_response']['fault'])
+        pid = iq['id']      
+        with self._lock:
+            callback = self._callbacks[pid]
+            del self._callbacks[pid]
+        e = {
+             500: InvocationException 
+        }[fault['code']](fault['string'])
+        callback.cancel_with_error(e)    
+
+    def _on_jabber_rpc_error(self, iq):
+        pid = iq['id']
+        pmethod = self._client.plugin['xep_0009']._extract_method(iq['rpc_query'])
+        code = iq['error']['code']        
+        type = iq['error']['type']
+        condition = iq['error']['condition']
+        #! print("['REMOTE.PY']._BINDING_handle_remote_procedure_error -> ERROR! ERROR! ERROR! Condition is '%s'" % condition)
+        with self._lock:
+            callback = self._callbacks[pid]
+            del self._callbacks[pid]
+        e = { 
+            'item-not-found': RemoteException("No remote handler available for %s at %s!" % (pmethod, iq['from'])),
+            'forbidden': AuthorizationException("Forbidden to invoke remote handler for %s at %s!" % (pmethod, iq['from'])),
+            'undefined-condition': RemoteException("An unexpected problem occured trying to invoke %s at %s!" % (pmethod, iq['from'])),            
+        }[condition]
+        if e is None:
+            RemoteException("An unexpected exception occurred at %s!" % iq['from'])
+        callback.cancel_with_error(e)
+
+
+
+class Remote(object):
+    '''
+    Bootstrap class for Jabber-RPC sessions. New sessions are openend
+    with an existing XMPP client, or one is instantiated on demand.
+    '''
+    _instance = None
+    _sessions = dict()
+    _lock = threading.RLock()
+    
+    @classmethod
+    def new_session_with_client(cls, client, callback=None):
+        '''
+        Opens a new session with a given client.
+        
+        Arguments:
+            client -- An XMPP client.
+            callback -- An optional callback which can be used to track
+                the starting state of the session.
+        '''
+        with Remote._lock:       
+            if(client.boundjid.bare in cls._sessions):
+                raise RemoteException("There already is a session associated with these credentials!")
+            else:
+                cls._sessions[client.boundjid.bare] = client;
+        def _session_close_callback():
+            with Remote._lock:
+                del cls._sessions[client.boundjid.bare]
+        result = RemoteSession(client, _session_close_callback)        
+        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_call', result._on_jabber_rpc_method_call)
+        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_response', result._on_jabber_rpc_method_response)
+        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_method_fault', result._on_jabber_rpc_method_fault)        
+        client.plugin['xep_0009'].xmpp.add_event_handler('jabber_rpc_error', result._on_jabber_rpc_error)
+        if callback is None:
+            start_event_handler = result._notify        
+        else:
+            start_event_handler = callback        
+        client.add_event_handler("session_start", start_event_handler)        
+        if client.connect():
+            client.process(threaded=True)
+        else:
+            raise RemoteException("Could not connect to XMPP server!")
+        pass    
+        if callback is None:
+            result._wait()
+        return result
+
+    @classmethod
+    def new_session(cls, jid, password, callback=None):
+        '''
+        Opens a new session and instantiates a new XMPP client.
+        
+        Arguments:
+            jid -- The XMPP JID for logging in.
+            password -- The password for logging in.
+            callback -- An optional callback which can be used to track
+                the starting state of the session.            
+        '''        
+        client = sleekxmpp.ClientXMPP(jid, password)
+        #? Register plug-ins.        
+        client.registerPlugin('xep_0004') # Data Forms
+        client.registerPlugin('xep_0009') # Jabber-RPC
+        client.registerPlugin('xep_0030') # Service Discovery
+        client.registerPlugin('xep_0060') # PubSub
+        client.registerPlugin('xep_0199') # XMPP Ping      
+        return cls.new_session_with_client(client, callback) 
+
+    
\ No newline at end of file
diff --git a/sleekxmpp/plugins/xep_0009/rpc.py b/sleekxmpp/plugins/xep_0009/rpc.py
new file mode 100644
index 00000000..84afeb5f
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0009/rpc.py
@@ -0,0 +1,221 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from .. import base
+from stanza.RPC import RPCQuery, MethodCall, MethodResponse
+from sleekxmpp.stanza.iq import Iq
+from sleekxmpp.xmlstream.handler.callback import Callback
+from sleekxmpp.xmlstream.matcher.xpath import MatchXPath
+from sleekxmpp.xmlstream.stanzabase import register_stanza_plugin
+from xml.etree import cElementTree as ET
+import logging
+
+
+
+log = logging.getLogger(__name__)
+
+
+
+class xep_0009(base.base_plugin):
+
+    def plugin_init(self):
+        self.xep = '0009'
+        self.description = 'Jabber-RPC'
+        #self.stanza = sleekxmpp.plugins.xep_0009.stanza
+        
+        register_stanza_plugin(Iq, RPCQuery)
+        register_stanza_plugin(RPCQuery, MethodCall)    
+        register_stanza_plugin(RPCQuery, MethodResponse)
+        
+        self.xmpp.registerHandler(
+            Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodCall' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
+            self._handle_method_call)
+        )        
+        self.xmpp.registerHandler(
+            Callback('RPC Call', MatchXPath('{%s}iq/{%s}query/{%s}methodResponse' % (self.xmpp.default_ns, RPCQuery.namespace, RPCQuery.namespace)),
+            self._handle_method_response)
+        )
+        self.xmpp.registerHandler(
+            Callback('RPC Call', MatchXPath('{%s}iq/{%s}error' % (self.xmpp.default_ns, self.xmpp.default_ns)),
+            self._handle_error)
+        )
+        self.xmpp.add_event_handler('jabber_rpc_method_call', self._on_jabber_rpc_method_call)
+        self.xmpp.add_event_handler('jabber_rpc_method_response', self._on_jabber_rpc_method_response)
+        self.xmpp.add_event_handler('jabber_rpc_method_fault', self._on_jabber_rpc_method_fault)        
+        self.xmpp.add_event_handler('jabber_rpc_error', self._on_jabber_rpc_error)
+        self.xmpp.add_event_handler('error', self._handle_error)
+        #self.activeCalls = []
+
+    def post_init(self):
+        base.base_plugin.post_init(self)
+        self.xmpp.plugin['xep_0030'].add_feature('jabber:iq:rpc')
+        self.xmpp.plugin['xep_0030'].add_identity('automation','rpc')            
+
+    def make_iq_method_call(self, pto, pmethod, params):
+        iq = self.xmpp.makeIqSet()
+        iq.attrib['to'] = pto
+        iq.attrib['from'] = self.xmpp.boundjid.full
+        iq.enable('rpc_query')
+        iq['rpc_query']['method_call']['method_name'] = pmethod
+        iq['rpc_query']['method_call']['params'] = params
+        return iq;
+    
+    def make_iq_method_response(self, pid, pto, params):
+        iq = self.xmpp.makeIqResult(pid)
+        iq.attrib['to'] = pto
+        iq.attrib['from'] = self.xmpp.boundjid.full
+        iq.enable('rpc_query')
+        iq['rpc_query']['method_response']['params'] = params
+        return iq
+
+    def make_iq_method_response_fault(self, pid, pto, params):
+        iq = self.xmpp.makeIqResult(pid)
+        iq.attrib['to'] = pto
+        iq.attrib['from'] = self.xmpp.boundjid.full
+        iq.enable('rpc_query')
+        iq['rpc_query']['method_response']['params'] = None        
+        iq['rpc_query']['method_response']['fault'] = params
+        return iq
+
+#    def make_iq_method_error(self, pto, pid, pmethod, params, code, type, condition):
+#        iq = self.xmpp.makeIqError(pid)
+#        iq.attrib['to'] = pto
+#        iq.attrib['from'] = self.xmpp.boundjid.full
+#        iq['error']['code'] = code
+#        iq['error']['type'] = type
+#        iq['error']['condition'] = condition
+#        iq['rpc_query']['method_call']['method_name'] = pmethod
+#        iq['rpc_query']['method_call']['params'] = params
+#        return iq
+
+    def _item_not_found(self, iq):
+        payload = iq.get_payload()
+        iq.reply().error().set_payload(payload);
+        iq['error']['code'] = '404'
+        iq['error']['type'] = 'cancel'
+        iq['error']['condition'] = 'item-not-found'
+        return iq
+    
+    def _undefined_condition(self, iq):
+        payload = iq.get_payload()        
+        iq.reply().error().set_payload(payload)    
+        iq['error']['code'] = '500'
+        iq['error']['type'] = 'cancel'
+        iq['error']['condition'] = 'undefined-condition'
+        return iq                    
+
+    def _forbidden(self, iq):
+        payload = iq.get_payload()
+        iq.reply().error().set_payload(payload)    
+        iq['error']['code'] = '403'
+        iq['error']['type'] = 'auth'
+        iq['error']['condition'] = 'forbidden'
+        return iq    
+
+    def _recipient_unvailable(self, iq):
+        payload = iq.get_payload()        
+        iq.reply().error().set_payload(payload)    
+        iq['error']['code'] = '404'
+        iq['error']['type'] = 'wait'
+        iq['error']['condition'] = 'recipient-unavailable'
+        return iq    
+
+    def _handle_method_call(self, iq):
+        type = iq['type']
+        if type == 'set':
+            log.debug("Incoming Jabber-RPC call from %s" % iq['from'])
+            self.xmpp.event('jabber_rpc_method_call', iq)            
+        else:
+            if type == 'error' and ['rpc_query'] is None:
+                self.handle_error(iq)
+            else:
+                log.debug("Incoming Jabber-RPC error from %s" % iq['from'])                
+                self.xmpp.event('jabber_rpc_error', iq)                   
+    
+    def _handle_method_response(self, iq):
+        if iq['rpc_query']['method_response']['fault'] is not None:
+            log.debug("Incoming Jabber-RPC fault from %s" % iq['from'])             
+            #self._on_jabber_rpc_method_fault(iq)
+            self.xmpp.event('jabber_rpc_method_fault', iq)
+        else:
+            log.debug("Incoming Jabber-RPC response from %s" % iq['from'])            
+            self.xmpp.event('jabber_rpc_method_response', iq)            
+        
+    def _handle_error(self, iq):
+        print("['XEP-0009']._handle_error -> ERROR! Iq is '%s'" % iq)
+        print("#######################")
+        print("### NOT IMPLEMENTED ###")        
+        print("#######################")
+                
+    def _on_jabber_rpc_method_call(self, iq, forwarded=False):
+        """
+        A default handler for Jabber-RPC method call. If another
+        handler is registered, this one will defer and not run.
+
+        If this handler is called by your own custom handler with
+        forwarded set to True, then it will run as normal.
+        """
+        if not forwarded and self.xmpp.event_handled('jabber_rpc_method_call') > 1:
+            return        
+        # Reply with error by default
+        error = self.client.plugin['xep_0009']._item_not_found(iq)
+        error.send()
+
+    def _on_jabber_rpc_method_response(self, iq, forwarded=False):
+        """
+        A default handler for Jabber-RPC method response. If another
+        handler is registered, this one will defer and not run.
+
+        If this handler is called by your own custom handler with
+        forwarded set to True, then it will run as normal.
+        """
+        if not forwarded and self.xmpp.event_handled('jabber_rpc_method_response') > 1:
+            return        
+        error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
+        error.send()
+
+    def _on_jabber_rpc_method_fault(self, iq, forwarded=False):
+        """
+        A default handler for Jabber-RPC fault response. If another
+        handler is registered, this one will defer and not run.
+
+        If this handler is called by your own custom handler with
+        forwarded set to True, then it will run as normal.
+        """        
+        if not forwarded and self.xmpp.event_handled('jabber_rpc_method_fault') > 1:
+            return            
+        error = self.client.plugin['xep_0009']._recpient_unavailable(iq)
+        error.send()
+    
+    def _on_jabber_rpc_error(self, iq, forwarded=False):
+        """
+        A default handler for Jabber-RPC error response. If another
+        handler is registered, this one will defer and not run.
+
+        If this handler is called by your own custom handler with
+        forwarded set to True, then it will run as normal.
+        """        
+        if not forwarded and self.xmpp.event_handled('jabber_rpc_error') > 1:
+            return                
+        error = self.client.plugin['xep_0009']._recpient_unavailable(iq, iq.get_payload())
+        error.send()
+            
+    def _send_fault(self, iq, fault_xml): # 
+        fault = self.make_iq_method_response_fault(iq['id'], iq['from'], fault_xml)
+        fault.send()        
+        
+    def _send_error(self, iq):
+        print("['XEP-0009']._send_error -> ERROR! Iq is '%s'" % iq)        
+        print("#######################")
+        print("### NOT IMPLEMENTED ###")        
+        print("#######################")
+    
+    def _extract_method(self, stanza):
+        xml = ET.fromstring("%s" % stanza)
+        return xml.find("./methodCall/methodName").text
+        
\ No newline at end of file
diff --git a/sleekxmpp/plugins/xep_0009/stanza/RPC.py b/sleekxmpp/plugins/xep_0009/stanza/RPC.py
new file mode 100644
index 00000000..24f2efd8
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0009/stanza/RPC.py
@@ -0,0 +1,68 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from sleekxmpp.xmlstream.stanzabase import ElementBase
+from xml.etree import cElementTree as ET
+
+
+class RPCQuery(ElementBase):
+    name = 'query'
+    namespace = 'jabber:iq:rpc'
+    plugin_attrib = 'rpc_query'
+    interfaces = set(())    
+    subinterfaces = set(())
+    plugin_attrib_map = {}
+    plugin_tag_map = {}
+
+
+
+class MethodCall(ElementBase):
+    name = 'methodCall'
+    namespace = 'jabber:iq:rpc'
+    plugin_attrib = 'method_call'
+    interfaces = set(('method_name', 'params'))    
+    subinterfaces = set(())
+    plugin_attrib_map = {}
+    plugin_tag_map = {}        
+
+
+    def get_method_name(self):
+        return self._get_sub_text('methodName')
+
+    def set_method_name(self, value):
+        return self._set_sub_text('methodName', value)
+    
+    def get_params(self):
+        return self.xml.find('{%s}params' % self.namespace)
+    
+    def set_params(self, params):
+        self.append(params)
+        
+
+        
+class MethodResponse(ElementBase):
+    name = 'methodResponse'
+    namespace = 'jabber:iq:rpc'
+    plugin_attrib = 'method_response'
+    interfaces = set(('params', 'fault'))    
+    subinterfaces = set(())
+    plugin_attrib_map = {}
+    plugin_tag_map = {}
+    
+    
+    def get_params(self):
+        return self.xml.find('{%s}params' % self.namespace)
+    
+    def set_params(self, params):
+        self.append(params)
+
+    def get_fault(self):
+        return self.xml.find('{%s}fault' % self.namespace)
+    
+    def set_fault(self, fault):
+        self.append(fault)
diff --git a/sleekxmpp/plugins/xep_0009/stanza/__init__.py b/sleekxmpp/plugins/xep_0009/stanza/__init__.py
new file mode 100644
index 00000000..0b902238
--- /dev/null
+++ b/sleekxmpp/plugins/xep_0009/stanza/__init__.py
@@ -0,0 +1,9 @@
+"""
+    SleekXMPP: The Sleek XMPP Library
+    Copyright (C) 2011 Nathanael C. Fritz, Dann Martens (TOMOTON).
+    This file is part of SleekXMPP.
+
+    See the file LICENSE for copying permission.
+"""
+
+from RPC import RPCQuery, MethodCall, MethodResponse
-- 
cgit v1.2.3


From 0a3a7b5a70dda56ffae4e85dc161b95072d3f085 Mon Sep 17 00:00:00 2001
From: Dann Martens <me@dannmartens.com>
Date: Thu, 13 Jan 2011 11:37:58 +0100
Subject: Removed binding XML namespace experiments.

---
 sleekxmpp/plugins/xep_0009/binding.py | 115 ----------------------------------
 1 file changed, 115 deletions(-)

(limited to 'sleekxmpp')

diff --git a/sleekxmpp/plugins/xep_0009/binding.py b/sleekxmpp/plugins/xep_0009/binding.py
index 6b50d99e..464081fc 100644
--- a/sleekxmpp/plugins/xep_0009/binding.py
+++ b/sleekxmpp/plugins/xep_0009/binding.py
@@ -91,121 +91,6 @@ def _py2xml(*args):
             val.append(struct)
         return val
 
-#def py2xml(*args):
-#    params = ET.Element("{%s}params" % _namespace)
-#    for x in args:
-#        param = ET.Element("{%s}param" % _namespace)
-#        param.append(_py2xml(x))
-#        params.append(param) #<params><param>...
-#    return params
-#
-#def _py2xml(*args):
-#    for x in args:
-#        val = ET.Element("{%s}value" % _namespace)
-#        if x is None:
-#            nil = ET.Element("{%s}nil" % _namespace)
-#            val.append(nil)
-#        elif type(x) is int:
-#            i4 = ET.Element("{%s}i4" % _namespace)
-#            i4.text = str(x)
-#            val.append(i4)
-#        elif type(x) is bool:
-#            boolean = ET.Element("{%s}boolean" % _namespace)
-#            boolean.text = str(int(x))
-#            val.append(boolean)
-#        elif type(x) is str:
-#            string = ET.Element("{%s}string" % _namespace)
-#            string.text = x
-#            val.append(string)
-#        elif type(x) is float:
-#            double = ET.Element("{%s}double" % _namespace)
-#            double.text = str(x)
-#            val.append(double)
-#        elif type(x) is rpcbase64:
-#            b64 = ET.Element("{%s}Base64" % _namespace)
-#            b64.text = x.encoded()
-#            val.append(b64)
-#        elif type(x) is rpctime:
-#            iso = ET.Element("{%s}dateTime.iso8601" % _namespace)
-#            iso.text = str(x)
-#            val.append(iso)
-#        elif type(x) in (list, tuple):
-#            array = ET.Element("{%s}array" % _namespace)
-#            data = ET.Element("{%s}data" % _namespace)
-#            for y in x:
-#                data.append(_py2xml(y))
-#            array.append(data)
-#            val.append(array)
-#        elif type(x) is dict:
-#            struct = ET.Element("{%s}struct" % _namespace)
-#            for y in x.keys():
-#                member = ET.Element("{%s}member" % _namespace)
-#                name = ET.Element("{%s}name" % _namespace)
-#                name.text = y
-#                member.append(name)
-#                member.append(_py2xml(x[y]))
-#                struct.append(member)
-#            val.append(struct)
-#        return val
-    
-
-#def py2xml(*args):
-#    params = ET.Element("params", {'xmlns': _namespace})
-#    for x in args:
-#        param = ET.Element("param", {'xmlns': _namespace})
-#        param.append(_py2xml(x))
-#        params.append(param) #<params><param>...
-#    return params
-#
-#def _py2xml(*args):
-#    for x in args:
-#        val = ET.Element("value", {'xmlns': _namespace})
-#        if x is None:
-#            nil = ET.Element("nil", {'xmlns': _namespace})
-#            val.append(nil)
-#        elif type(x) is int:
-#            i4 = ET.Element("i4", {'xmlns': _namespace})
-#            i4.text = str(x)
-#            val.append(i4)
-#        elif type(x) is bool:
-#            boolean = ET.Element("boolean", {'xmlns': _namespace})
-#            boolean.text = str(int(x))
-#            val.append(boolean)
-#        elif type(x) is str:
-#            string = ET.Element("string", {'xmlns': _namespace})
-#            string.text = x
-#            val.append(string)
-#        elif type(x) is float:
-#            double = ET.Element("double", {'xmlns': _namespace})
-#            double.text = str(x)
-#            val.append(double)
-#        elif type(x) is rpcbase64:
-#            b64 = ET.Element("Base64", {'xmlns': _namespace})
-#            b64.text = x.encoded()
-#            val.append(b64)
-#        elif type(x) is rpctime:
-#            iso = ET.Element("dateTime.iso8601", {'xmlns': _namespace})
-#            iso.text = str(x)
-#            val.append(iso)
-#        elif type(x) in (list, tuple):
-#            array = ET.Element("array", {'xmlns': _namespace})
-#            data = ET.Element("data", {'xmlns': _namespace})
-#            for y in x:
-#                data.append(_py2xml(y))
-#            array.append(data)
-#            val.append(array)
-#        elif type(x) is dict:
-#            struct = ET.Element("struct", {'xmlns': _namespace})
-#            for y in x.keys():
-#                member = ET.Element("member", {'xmlns': _namespace})
-#                name = ET.Element("name", {'xmlns': _namespace})
-#                name.text = y
-#                member.append(name)
-#                member.append(_py2xml(x[y]))
-#                struct.append(member)
-#            val.append(struct)
-#        return val
-
 def xml2py(params):
     namespace = 'jabber:iq:rpc'
     vals = []
-- 
cgit v1.2.3


From 4be6482ff3278612365863575dceeda9fd9a88c3 Mon Sep 17 00:00:00 2001
From: Dann Martens <me@dannmartens.com>
Date: Thu, 13 Jan 2011 13:42:01 +0100
Subject: Fixed 'nil' bug in unmarshalling.

---
 sleekxmpp/plugins/xep_0009/binding.py | 2 ++
 1 file changed, 2 insertions(+)

(limited to 'sleekxmpp')

diff --git a/sleekxmpp/plugins/xep_0009/binding.py b/sleekxmpp/plugins/xep_0009/binding.py
index 464081fc..61ef1469 100644
--- a/sleekxmpp/plugins/xep_0009/binding.py
+++ b/sleekxmpp/plugins/xep_0009/binding.py
@@ -100,6 +100,8 @@ def xml2py(params):
 
 def _xml2py(value):
     namespace = 'jabber:iq:rpc'
+    if value.find('{%s}nil' % namespace) is not None:
+        return None    
     if value.find('{%s}i4' % namespace) is not None:
         return int(value.find('{%s}i4' % namespace).text)
     if value.find('{%s}int' % namespace) is not None:
-- 
cgit v1.2.3


From b68e7bed403924dc4ebf7294854d4892c48ce0ab Mon Sep 17 00:00:00 2001
From: Dann Martens <me@dannmartens.com>
Date: Thu, 13 Jan 2011 15:04:16 +0100
Subject: Fixed typo.

---
 sleekxmpp/basexmpp.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'sleekxmpp')

diff --git a/sleekxmpp/basexmpp.py b/sleekxmpp/basexmpp.py
index 42cbaa04..e3c7bc5a 100644
--- a/sleekxmpp/basexmpp.py
+++ b/sleekxmpp/basexmpp.py
@@ -245,7 +245,7 @@ class BaseXMPP(XMLStream):
         """Create a Presence stanza associated with this stream."""
         return Presence(self, *args, **kwargs)
 
-    def make_iq(self, id=0, ifrom=None, ito=None, type=None, query=None):
+    def make_iq(self, id=0, ifrom=None, ito=None, itype=None, query=None):
         """
         Create a new Iq stanza with a given Id and from JID.
 
-- 
cgit v1.2.3