1 # Cloudeebus for Crosswalk
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 # Patrick Ohly <patrick.ohly@intel.com>
20 # This is an extension loaded by pycrosswalk. It uses Cloudeebus
21 # Python in the Crosswalk extension process and Cloudeebus JavaScript
22 # in the Crosswalk render process, connected via Crosswalk's extension
23 # message passing instead of the original WebSocket/WAMP.
26 # cloudeebus.js, xwalkcloudeebus.py and engine.py must be installed in
27 # the same directory. xwalkcloudeebus.py (or a symlink to it) and a
28 # symlink to libpycrosswalk.so must be in a directory that Crosswalk
29 # searches for extensions.
31 # To run some examples directly in the cloudeebus source tree:
32 # ln -s <path to libpycrosswalk.so> cloudeebus/libxwalkcloudeebus.so
33 # xwalk --external-extensions-path=cloudeebus doc/agent/server.html &
34 # xwalk --external-extensions-path=cloudeebus doc/agent/client.html &
36 # Only one-way messages are used. RPC method calls contain a sequence
37 # number that gets repeated in the reply, so the caller can match
38 # pending calls with their reply.
40 # The message format is JSON:
42 # <type> = "call" | "reply" | "signal"
43 # [ "call", <sequence number>, <method name>, [<parameters>] ]
44 # [ "reply", <call sequence number>, "error string", <result> ]
45 # [ "signal", <topic>, [<parameters>] ]
46 # [ "subscribe", <topic> ]
47 # [ "unsubscribe", <topic> ]
58 from gi.repository import GLib
60 from dbus.mainloop.glib import DBusGMainLoop
61 DBusGMainLoop(set_as_default=True)
63 from twisted.internet import defer
64 from twisted.python import log
66 #log.startLogging(sys.stdout)
70 # Configure engine module. Partly has to be done before importing
71 # because the engine needs to know how it is going to be used.
72 os.environ['CLOUDEEBUS_XWALK'] = '1'
74 engine.OPENDOOR = True # No other process has access, so we need no additional credential checking.
77 # Mapping from instance ID to hash with all subscribed topics.
79 def dispatch(self, topic, event):
80 for instance, topics in Factory.instances.iteritems():
82 xwalk.PostMessage(instance, json.dumps(['signal', topic, event]))
84 engine.factory = Factory()
86 service = engine.CloudeebusService({'permissions': [], 'authextra': '', 'services': []})
89 for method in inspect.getmembers(service.__class__, inspect.ismethod):
90 if method[1].__dict__.has_key("_xwalk_rpc_id"):
91 name = method[1].__dict__["_xwalk_rpc_id"]
95 def HandleMessage(instance, message):
96 log.msg('New message: %s' % message)
97 content = json.loads(message)
100 sequencenr = content[1]
102 name = str(content[2])
104 d = defer.maybeDeferred(methods[name], service, params)
105 d.addCallback(lambda result: (log.msg('call %d done: %s' % (sequencenr, result)), xwalk.PostMessage(instance, json.dumps(['reply', sequencenr, '', result]))))
106 d.addErrback(lambda error: (log.msg('call %d failed: %s' % (sequencenr, error)), xwalk.PostMessage(instance, json.dumps(['reply', sequencenr, str(error), []]))))
107 except Exception, ex:
108 log.msg('failed to start call %d: %s' % (sequencenr, traceback.format_exc()));
109 xwalk.PostMessage(instance, json.dumps(['reply', sequencenr, repr(ex), []]))
110 elif msgtype == 'subscribe':
112 log.msg('Subscribing %d to %s' % (instance, topic))
113 Factory.instances[instance][topic] = True
114 elif msgtype == 'unsubscribe':
116 log.msg('Unsubscribing %d from %s' % (instance, topic))
117 del Factory.instances[instance][topic]
119 def HandleInstanceCreated(instance):
120 Factory.instances[instance] = {}
121 xwalk.SetMessageCallback(instance, HandleMessage)
123 def HandleInstanceDestroyed(instance):
124 del Factory.instances[instance]
127 xwalk.SetExtensionName("cloudeebus")
128 xwalk.SetInstanceCreatedCallback(HandleInstanceCreated)
129 xwalk.SetInstanceDestroyedCallback(HandleInstanceDestroyed)
131 # cloudeebus.js is expected in the same directory as the actual
132 # xwalkcloudeebus.py file (i.e., after resolving symlinks).
133 modpath = inspect.getsourcefile(Main)
134 modpath = os.path.realpath(modpath)
135 jssource = os.path.join(os.path.dirname(modpath), 'cloudeebus.js')
137 js = open(jssource).read()
140 var pending_calls = {};
142 var call_counter = 1;
144 // A pending call behaves like a Promise: the instance
145 // gets stored in the pending hash, is returned by call(),
146 // and then the caller installs its callbacks with then().
147 var Pending = function() {
152 Pending.prototype.then = function(success, failure) {
153 this.success = success;
154 this.failure = failure;
157 // Error instance as used by WAMP error callbacks.
158 // Meant to work with cloudeebus.getError().
159 var Error = function(description) {
160 this.desc = description;
167 extension.setMessageListener(function(msg) {
168 var msg_content = JSON.parse(msg);
169 if (msg_content[0] == "reply") {
170 // Handle message reply.
171 var pending = pending_calls[msg_content[1]];
172 delete pending_calls[msg_content[1]];
173 if (msg_content[2] != "") {
174 if (pending.failure) {
175 pending.failure(msg_content[2]);
178 if (pending.success) {
179 pending.success(msg_content[3]);
183 if (msg_content[0] == "signal") {
185 var topic = msg_content[1];
186 var args = msg_content[2];
187 var handler = topics[topic];
189 handler(topic, args);
194 // Emulate WAMPSession.
195 var Session = function() {
196 this.extension = extension;
199 Session.prototype.call = function(method, args) {
200 var message = [ "call", call_counter, method, args ];
201 var data = JSON.stringify(message);
202 var pending = new Pending();
203 pending_calls[call_counter] = pending;
204 this.extension.postMessage(data);
208 Session.prototype.subscribe = function(topic, handler) {
209 var message = [ "subscribe", topic ]
210 var data = JSON.stringify(message);
211 this.extension.postMessage(data);
212 topics[topic] = handler;
214 Session.prototype.unsubscribe = function(topic) {
215 var message = [ "unsubscribe", topic ]
216 var data = JSON.stringify(message);
217 this.extension.postMessage(data);
218 delete topics[topic];
220 var session = new Session();
222 exports.connect = function(uri, manifest, successCB, errorCB) {
224 cloudeebus.sessionBus = new cloudeebus.BusConnection("session", session);
225 cloudeebus.systemBus = new cloudeebus.BusConnection("system", session);
228 exports.SessionBus = cloudeebus.SessionBus;
229 exports.SystemBus = cloudeebus.SystemBus;
230 exports.reset = cloudeebus.reset;
231 exports.Agent = cloudeebus.Agent;
232 exports.Service = cloudeebus.Service;
233 exports.ProxyObject = cloudeebus.ProxyObject;
234 exports.Promise = cloudeebus.Promise;
237 xwalk.SetJavaScriptAPI(js)