3 # Copyright 2012 Intel Corporation.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 # Luc Yriarte <luc.yriarte@intel.com>
18 # Christophe Guiraud <christophe.guiraud@intel.com>
19 # Frederic Paut <frederic.paut@intel.com>
29 from twisted.python import log
32 from xml.etree.ElementTree import XMLParser
34 from twisted.internet import defer
36 # The user of cloudeebusengine.py must set this to some object
37 # providing a dispatch(topicUri, event) method as in WampServerFactory
40 if os.environ.get('CLOUDEEBUS_XWALK', False):
41 # Same approach as in autobahn.wamp: add the method name to
42 # decorated methods, which then gets used to identify the methods
43 # that can be called from remote.
45 arg._xwalk_rpc_id = arg.__name__
48 from autobahn.wamp import exportRpc
54 ###############################################################################
57 Global cache of DBus connexions and signal handlers
60 self.dbusConnexions = {}
61 self.signalHandlers = {}
66 Disconnect signal handlers before resetting cache.
68 self.dbusConnexions = {}
69 # disconnect signal handlers
70 for key in self.signalHandlers:
71 self.signalHandlers[key].disconnect()
72 self.signalHandlers = {}
75 def dbusConnexion(self, busName):
76 if not self.dbusConnexions.has_key(busName):
77 if busName == "session":
78 self.dbusConnexions[busName] = dbus.SessionBus()
79 elif busName == "system":
80 self.dbusConnexions[busName] = dbus.SystemBus()
82 raise Exception("Error: invalid bus: %s" % busName)
83 return self.dbusConnexions[busName]
88 ###############################################################################
89 class DbusSignalHandler:
91 signal hash id as busName#senderName#objectName#interfaceName#signalName
93 def __init__(self, busName, senderName, objectName, interfaceName, signalName):
94 self.id = "#".join([busName, senderName, objectName, interfaceName, signalName])
95 # connect handler to signal
96 self.bus = cache.dbusConnexion(busName)
97 self.bus.add_signal_receiver(self.handleSignal, signalName, interfaceName, senderName, objectName)
100 def disconnect(self):
101 names = self.id.split("#")
102 self.bus.remove_signal_receiver(self.handleSignal, names[4], names[3], names[1], names[2])
105 def handleSignal(self, *args):
107 publish dbus args under topic hash id
109 factory.dispatch(self.id, json.dumps(args))
113 ###############################################################################
114 class DbusCallHandler:
116 deferred reply to return dbus results
118 def __init__(self, method, args):
120 self.request = defer.Deferred()
125 def callMethod(self):
127 dbus method async call
130 self.method(*self.args, reply_handler=self.dbusSuccess, error_handler=self.dbusError)
134 def dbusSuccess(self, *result):
136 return JSON string result array
138 self.request.callback(json.dumps(result))
142 def dbusError(self, error):
144 return dbus error message
146 self.request.errback(Exception(error.get_dbus_message()))
151 ################################################################################
154 Execute DynDBusClass generated code
156 def __init__(self, globalCtx, localCtx) :
157 self.exec_string = ""
158 self.exec_code = None
159 self.exec_code_valid = 1
160 self.indent_level = 0
161 self.indent_increment = 1
163 self.localCtx = localCtx
164 self.globalCtx = globalCtx
167 def append_stmt(self, stmt) :
168 self.exec_code_valid = 0
170 for x in range(0,self.indent_level):
171 self.exec_string = self.exec_string + ' '
172 self.exec_string = self.exec_string + stmt + '\n'
175 self.indent_level = self.indent_level + self.indent_increment
178 self.indent_level = self.indent_level - self.indent_increment
180 # compile : Compile exec_string into exec_code using the builtin
181 # compile function. Skip if already in sync.
183 if not self.exec_code_valid :
184 self.exec_code = compile(self.exec_string, "<string>", "exec")
185 self.exec_code_valid = True
188 if not self.exec_code_valid :
190 exec(self.exec_code, self.globalCtx, self.localCtx)
194 ################################################################################
195 class XmlCbParser: # The target object of the parser
198 def __init__(self, dynDBusClass):
199 self.dynDBusClass = dynDBusClass
201 def start(self, tag, attrib): # Called for each opening tag.
205 if (tag == 'interface'):
206 self.dynDBusClass.set_interface(attrib['name'])
209 if (tag == 'method'):
211 self.dynDBusClass.def_method(attrib['name'])
213 if (tag == 'signal'):
215 self.dynDBusClass.def_signal(attrib['name'])
218 # Set signature (in/out & name) for method
220 if (self.current == 'method'):
221 if (attrib.has_key('direction') == False):
222 attrib['direction'] = "in"
223 self.dynDBusClass.add_signature(attrib['name'],
227 if (self.current == 'signal'):
228 if (attrib.has_key('name') == False):
229 attrib['name'] = 'value'
230 self.dynDBusClass.add_signature(attrib['name'], 'in',
233 def end(self, tag): # Called for each closing tag.
234 if (tag == 'method'):
235 self.dynDBusClass.add_dbus_method()
236 self.dynDBusClass.add_body_method()
237 self.dynDBusClass.end_method()
238 if (tag == 'signal'):
239 self.dynDBusClass.add_dbus_signal()
240 self.dynDBusClass.add_body_signal()
241 self.dynDBusClass.end_method()
243 def data(self, data):
244 pass # We do not need to do anything with data.
245 def close(self): # Called when all data has been parsed.
250 ###############################################################################
251 def createClassName(objectPath):
252 return re.sub('/', '_', objectPath[1:])
254 ################################################################################
255 class DynDBusClass():
256 def __init__(self, className, globalCtx, localCtx):
257 self.xmlCB = XmlCbParser(self)
259 self.class_code = ExecCode(globalCtx, localCtx)
260 self.class_code.indent_increment = 4
261 self.class_code.append_stmt("import dbus")
262 self.class_code.append_stmt("\n")
263 self.class_code.append_stmt("class " + className + "(dbus.service.Object):")
264 self.class_code.indent()
266 ## Overload of __init__ method
267 self.def_method("__init__")
268 self.add_method("bus, callback=None, objPath='/sample', srvName='org.cloudeebus'")
269 self.add_stmt("self.bus = bus")
270 self.add_stmt("self.objPath = objPath")
271 self.add_stmt("self.srvName = srvName")
272 self.add_stmt("self.callback = callback")
273 self.add_stmt("dbus.service.Object.__init__(self, conn=bus, bus_name=srvName)")
276 ## Create 'add_to_connection' method
277 self.def_method("add_to_connection")
278 self.add_method("connection=None, path=None")
279 self.add_stmt("dbus.service.Object.add_to_connection(self, connection=self.bus, path=self.objPath)")
282 ## Create 'remove_from_connection' method
283 self.def_method("remove_from_connection")
284 self.add_method("connection=None, path=None")
285 self.add_stmt("dbus.service.Object.remove_from_connection(self, connection=None, path=self.objPath)")
288 def createDBusServiceFromXML(self, xml):
289 self.parser = XMLParser(target=self.xmlCB)
290 self.parser.feed(xml)
293 def set_interface(self, ifName):
296 def def_method(self, methodName):
297 self.methodToAdd = methodName
298 self.signalToAdd = None
299 self.args_str = str()
301 self.signature['name'] = str()
302 self.signature['in'] = str()
303 self.signature['out'] = str()
305 def def_signal(self, signalName):
306 self.methodToAdd = None
307 self.signalToAdd = signalName
308 self.args_str = str()
310 self.signature['name'] = str()
311 self.signature['in'] = str()
312 self.signature['out'] = str()
314 def add_signature(self, name, direction, signature):
315 if (direction == 'in'):
316 self.signature['in'] += signature
317 if (self.signature['name'] != str()):
318 self.signature['name'] += ", "
319 self.signature['name'] += name
320 if (direction == 'out'):
321 self.signature['out'] = signature
323 def add_method(self, args = None, async_success_cb = None, async_err_cb = None):
325 if (self.methodToAdd != None):
326 name = self.methodToAdd
328 name = self.signalToAdd
331 if (async_success_cb != None):
332 async_cb_str = async_success_cb
333 if (async_err_cb != None):
334 if (async_cb_str != str()):
336 async_cb_str += async_err_cb
338 parameters = self.args_str
339 if (async_cb_str != str()):
340 if (parameters != str()):
342 parameters +=async_cb_str
344 if (parameters != str()):
345 self.class_code.append_stmt("def " + name + "(self, %s):" % parameters)
347 self.class_code.append_stmt("def " + name + "(self):")
348 self.class_code.indent()
350 def end_method(self):
351 self.class_code.append_stmt("\n")
352 self.class_code.dedent()
354 def add_dbus_method(self):
355 decorator = '@dbus.service.method("' + self.ifName + '"'
356 if (self.signature.has_key('in') and self.signature['in'] != str()):
357 decorator += ", in_signature='" + self.signature['in'] + "'"
358 if (self.signature.has_key('out') and self.signature['out'] != str()):
359 decorator += ", out_signature='" + self.signature['out'] + "'"
360 decorator += ", async_callbacks=('dbus_async_cb', 'dbus_async_err_cb')"
362 self.class_code.append_stmt(decorator)
363 if (self.signature.has_key('name') and self.signature['name'] != str()):
364 self.add_method(self.signature['name'], async_success_cb='dbus_async_cb', async_err_cb='dbus_async_err_cb')
366 self.add_method(async_success_cb='dbus_async_cb', async_err_cb='dbus_async_err_cb')
368 def add_dbus_signal(self):
369 decorator = '@dbus.service.signal("' + self.ifName + '"'
370 if (self.signature.has_key('in') and self.signature['in'] != str()):
371 decorator += ", signature='" + self.signature['in'] + "'"
373 self.class_code.append_stmt(decorator)
374 if (self.signature.has_key('name') and self.signature['name'] != str()):
375 self.add_method(self.signature['name'])
379 def add_body_method(self):
380 if (self.methodToAdd != None):
381 if (self.args_str != str()):
382 self.class_code.append_stmt("self.callback(self.srvName,'" + self.methodToAdd + "', self.objPath, '" + self.ifName + "', " + "dbus_async_cb, dbus_async_err_cb, %s)" % self.args_str)
384 self.class_code.append_stmt("self.callback(self.srvName,'" + self.methodToAdd + "', self.objPath, '" + self.ifName + "', " + "dbus_async_cb, dbus_async_err_cb)")
386 def add_body_signal(self):
387 self.class_code.append_stmt("return") ## TODO: Remove and fix with code ad hoc
388 self.class_code.append_stmt("\n")
390 def add_stmt(self, stmt) :
391 self.class_code.append_stmt(stmt)
394 self.class_code.execute()
398 ###############################################################################
399 class CloudeebusService:
401 support for sending DBus messages and registering for DBus signals
403 def __init__(self, permissions):
404 self.permissions = {};
405 self.permissions['permissions'] = permissions['permissions']
406 self.permissions['authextra'] = permissions['authextra']
407 self.permissions['services'] = permissions['services']
408 self.proxyObjects = {}
409 self.proxyMethods = {}
410 self.pendingCalls = []
411 self.dynDBusClasses = {} # DBus class source code generated dynamically (a list because one by classname)
412 self.services = {} # DBus service created
413 self.serviceAgents = {} # Instantiated DBus class previously generated dynamically, for now, one by classname
414 self.servicePendingCalls = {} # JS methods called (and waiting for a Success/error response), containing 'methodId', (successCB, errorCB)
415 self.localCtx = locals()
416 self.globalCtx = globals()
418 self.patternDbus = re.compile('^dbus\.(\w+)')
419 self.patternDbusBoolean = re.compile('^dbus.Boolean\((\w+)\)$')
420 self.patternDbusByte = re.compile('^dbus.Byte\((\d+)\)$')
421 self.patternDbusInt16 = re.compile('^dbus.Int16\((\d+)\)$')
422 self.patternDbusInt32 = re.compile('^dbus.Int32\((\d+)\)$')
423 self.patternDbusInt64 = re.compile('^dbus.Int64\((\d+)\)$')
424 self.patternDbusUInt16 = re.compile('^dbus.UInt16\((\d+)\)$')
425 self.patternDbusUInt32 = re.compile('^dbus.UInt32\((\d+)\)$')
426 self.patternDbusUInt64 = re.compile('^dbus.UInt64\((\d+)\)$')
427 self.patternDbusDouble = re.compile('^dbus.Double\((\d+\.\d+)\)$')
429 def proxyObject(self, busName, serviceName, objectName):
431 object hash id as busName#serviceName#objectName
433 id = "#".join([busName, serviceName, objectName])
434 if not self.proxyObjects.has_key(id):
436 # check permissions, array.index throws exception
437 self.permissions['permissions'].index(serviceName)
438 bus = cache.dbusConnexion(busName)
439 self.proxyObjects[id] = bus.get_object(serviceName, objectName)
440 return self.proxyObjects[id]
443 def proxyMethod(self, busName, serviceName, objectName, interfaceName, methodName):
445 method hash id as busName#serviceName#objectName#interfaceName#methodName
447 id = "#".join([busName, serviceName, objectName, interfaceName, methodName])
448 if not self.proxyMethods.has_key(id):
449 obj = self.proxyObject(busName, serviceName, objectName)
450 self.proxyMethods[id] = obj.get_dbus_method(methodName, interfaceName)
451 return self.proxyMethods[id]
453 def decodeArgs( self, args ):
454 if isinstance( args, list ):
457 newArgs.append( self.decodeArgs( arg ))
459 elif isinstance( args, dict ):
461 for key, value in args.iteritems():
462 key = self.decodeArgs( key )
463 newValue = self.decodeArgs( value )
464 newDict[key] = newValue
466 elif isinstance( args, basestring ):
467 newArg = self.decodeDbusString( args )
472 def decodeDbusString( self, dbusString ):
473 matchDbus = self.patternDbus.match( dbusString )
480 "Boolean" : lambda x : dbus.Boolean( self.patternDbusBoolean.match( x ).group( 1 ).lower() in ("yes", "true", "t", "1")),
481 "Byte" : lambda x : dbus.Byte( int( self.patternDbusByte.match( x ).group( 1 ))),
482 "Int16" : lambda x : dbus.Int16( self.patternDbusInt16.match( x ).group( 1 )),
483 "Int32" : lambda x : dbus.Int32( self.patternDbusInt32.match( x ).group( 1 )),
484 "Int64" : lambda x : dbus.Int64( self.patternDbusInt64.match( x ).group( 1 )),
485 "UInt16" : lambda x : dbus.UInt16( self.patternDbusUInt16.match( x ).group( 1 )),
486 "UInt32" : lambda x : dbus.UInt32( self.patternDbusUInt32.match( x ).group( 1 )),
487 "UInt64" : lambda x : dbus.UInt64( self.patternDbusUInt64.match( x ).group( 1 )),
488 "Double" : lambda x : dbus.Double( self.patternDbusDouble.match( x ).group( 1 ))
489 }[matchDbus.group(1)](dbusString)
494 def dbusRegister(self, list):
496 arguments: bus, sender, object, interface, signal
499 raise Exception("Error: expected arguments: bus, sender, object, interface, signal)")
502 # check permissions, array.index throws exception
503 self.permissions['permissions'].index(list[1])
505 # check if a handler exists
506 sigId = "#".join(list)
507 if cache.signalHandlers.has_key(sigId):
510 # create a handler that will publish the signal
511 dbusSignalHandler = DbusSignalHandler(*list)
512 cache.signalHandlers[sigId] = dbusSignalHandler
514 return dbusSignalHandler.id
518 def dbusSend(self, list):
520 arguments: bus, destination, object, interface, message, [args]
522 # clear pending calls
523 for call in self.pendingCalls:
525 self.pendingCalls.remove(call)
528 raise Exception("Error: expected arguments: bus, destination, object, interface, message, [args])")
530 # parse JSON arg list
533 jsonArgs = json.loads(list[5])
535 args = self.decodeArgs( jsonArgs )
537 # get dbus proxy method
538 method = self.proxyMethod(*list[0:5])
540 # use a deferred call handler to manage dbus results
541 dbusCallHandler = DbusCallHandler(method, args)
542 self.pendingCalls.append(dbusCallHandler)
543 return dbusCallHandler.callMethod()
547 def emitSignal(self, list):
549 arguments: agentObjectPath, signalName, args (to emit)
552 className = re.sub('/', '_', objectPath[1:])
555 jsonArgs = json.loads(list[2])
556 print "JSON Arguments:", jsonArgs
558 args = self.decodeArgs( jsonArgs )
559 print "Decoded Arguments: ", args
561 if (self.serviceAgents.has_key(className) == True):
562 exe_str = "self.serviceAgents['"+ className +"']."+ signalName + "("
564 exe_str += json.dumps(args[0])
566 exe_str += "," + json.dumps(idx)
568 eval(exe_str, self.globalCtx, self.localCtx)
570 raise Exception("No object path " + objectPath)
573 def returnMethod(self, list):
575 arguments: methodId, callIndex, success (=true, error otherwise), result (to return)
581 if (self.servicePendingCalls.has_key(methodId)):
582 cb = self.servicePendingCalls[methodId]['calls'][callIndex]
584 raise Exception("No pending call " + str(callIndex) + " for methodID " + methodId)
586 successCB = cb["successCB"]
592 errorCB = cb["errorCB"]
597 self.servicePendingCalls[methodId]['calls'][callIndex] = None
598 self.servicePendingCalls[methodId]['count'] = self.servicePendingCalls[methodId]['count'] - 1
599 if self.servicePendingCalls[methodId]['count'] == 0:
600 del self.servicePendingCalls[methodId]
602 raise Exception("No methodID " + methodId)
604 def srvCB(self, srvName, name, objPath, ifName, async_succes_cb, async_error_cb, *args):
605 methodId = srvName + "#" + objPath + "#" + ifName + "#" + name
606 cb = { 'successCB': async_succes_cb,
607 'errorCB': async_error_cb}
608 if methodId not in self.servicePendingCalls:
609 self.servicePendingCalls[methodId] = {'count': 0, 'calls': []}
612 pendingCallStr = json.dumps({'callIndex': len(self.servicePendingCalls[methodId]['calls']), 'args': args})
614 args = eval( str(args).replace("dbus.Byte", "dbus.Int16") )
615 pendingCallStr = json.dumps({'callIndex': len(self.servicePendingCalls[methodId]['calls']), 'args': args})
617 self.servicePendingCalls[methodId]['calls'].append(cb)
618 self.servicePendingCalls[methodId]['count'] = self.servicePendingCalls[methodId]['count'] + 1
619 factory.dispatch(methodId, pendingCallStr)
622 def serviceAdd(self, list):
624 arguments: busName, srvName
627 self.bus = cache.dbusConnexion( busName )
629 if not OPENDOOR and (SERVICELIST == [] or SERVICELIST != [] and self.permissions['services'] == None):
630 SERVICELIST.index(srvName)
632 if (self.services.has_key(srvName) == False):
633 self.services[srvName] = dbus.service.BusName(name = srvName, bus = self.bus)
637 def serviceRelease(self, list):
639 arguments: busName, srvName
642 if (self.services.has_key(srvName) == True):
643 self.services.pop(srvName)
646 raise Exception(srvName + " does not exist")
649 def serviceAddAgent(self, list):
651 arguments: objectPath, xmlTemplate
654 agentObjectPath = list[1]
655 xmlTemplate = list[2]
656 className = createClassName(agentObjectPath)
657 if (self.dynDBusClasses.has_key(className) == False):
658 self.dynDBusClasses[className] = DynDBusClass(className, self.globalCtx, self.localCtx)
659 self.dynDBusClasses[className].createDBusServiceFromXML(xmlTemplate)
660 self.dynDBusClasses[className].declare()
662 ## Class already exist, instanciate it if not already instanciated
663 if (self.serviceAgents.has_key(className) == False):
664 self.serviceAgents[className] = eval(className + "(self.bus, callback=self.srvCB, objPath='" + agentObjectPath + "', srvName='" + srvName + "')", self.globalCtx, self.localCtx)
666 self.serviceAgents[className].add_to_connection()
667 return (agentObjectPath)
670 def serviceDelAgent(self, list):
672 arguments: objectPath, xmlTemplate
674 agentObjectPath = list[0]
675 className = createClassName(agentObjectPath)
677 if (self.serviceAgents.has_key(className)):
678 self.serviceAgents[className].remove_from_connection()
679 self.serviceAgents.pop(className)
681 raise Exception(agentObjectPath + " doesn't exist!")
683 return (agentObjectPath)
686 def getVersion(self):
688 return current version string