[gjs/wip/gobj-kitchen-sink: 15/23] GDBus: introduce new convenience wrappers
- From: Jasper St. Pierre <jstpierre src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/wip/gobj-kitchen-sink: 15/23] GDBus: introduce new convenience wrappers
- Date: Thu, 2 Feb 2012 22:09:15 +0000 (UTC)
commit 6d3430bf94e24b6f166df8365ad8cc31a627559d
Author: Giovanni Campagna <gcampagna src gnome org>
Date: Thu Dec 8 00:20:09 2011 +0100
GDBus: introduce new convenience wrappers
Using the new metaclass system, introduce Gio.DBusProxyClass and
Gio.DBusImplementerClass, that allow declaring specialized proxies/
implementations for specific interfaces.
modules/overrides/Gio.js | 324 ++++++++++++++++++++++++++++++----------------
test/js/testGDBus.js | 128 +++++++++++--------
2 files changed, 288 insertions(+), 164 deletions(-)
---
diff --git a/modules/overrides/Gio.js b/modules/overrides/Gio.js
index b8cf013..114c3d6 100644
--- a/modules/overrides/Gio.js
+++ b/modules/overrides/Gio.js
@@ -20,6 +20,7 @@
// IN THE SOFTWARE.
var GLib = imports.gi.GLib;
+var GObject = imports.gi.GObject;
var GjsDBus = imports.gi.GjsDBus;
var Lang = imports.lang;
var Signals = imports.signals;
@@ -110,30 +111,12 @@ function _logReply(result, exc) {
}
}
-function _makeProxyMethod(method, sync) {
- /* JSON methods are the default */
- var i;
- var name = method.name;
- var inArgs = method.in_args;
- var inSignature = [ ];
- for (i = 0; i < inArgs.length; i++)
- inSignature.push(inArgs[i].signature);
-
- return function() {
- return _proxyInvoker.call(this, name, sync, inSignature, arguments);
- };
-}
-
-function _convertToNativeSignal(proxy, sender_name, signal_name, parameters) {
- Signals._emit.call(proxy, signal_name, sender_name, parameters.deep_unpack());
-}
-
function _propertyGetter(name) {
let value = this.get_cached_property(name);
return value ? value.deep_unpack() : null;
}
-function _propertySetter(value, name, signature) {
+function _propertySetter(name, value, signature) {
let variant = GLib.Variant.new(signature, value);
this.set_cached_property(name, variant);
@@ -152,56 +135,6 @@ function _propertySetter(value, name, signature) {
}));
}
-function _addDBusConvenience() {
- let info = this.g_interface_info;
- if (!info)
- return;
-
- if (info.signals.length > 0)
- this.connect('g-signal', _convertToNativeSignal);
-
- let i, methods = info.methods;
- for (i = 0; i < methods.length; i++) {
- var method = methods[i];
- this[method.name + 'Remote'] = _makeProxyMethod(methods[i], false);
- this[method.name + 'Sync'] = _makeProxyMethod(methods[i], true);
- }
-
- let properties = info.properties;
- for (i = 0; i < properties.length; i++) {
- let name = properties[i].name;
- let signature = properties[i].signature;
- Lang.defineAccessorProperty(this, name,
- Lang.bind(this, _propertyGetter, name),
- Lang.bind(this, _propertySetter, name, signature));
- }
-}
-
-function _makeProxyWrapper(interfaceXml) {
- var info = _newInterfaceInfo(interfaceXml);
- var iname = info.name;
- return function(bus, name, object, asyncCallback, cancellable) {
- var obj = new Gio.DBusProxy({ g_connection: bus,
- g_interface_name: iname,
- g_interface_info: info,
- g_name: name,
- g_object_path: object });
- if (!cancellable)
- cancellable = null;
- if (asyncCallback)
- obj.init_async(GLib.PRIORITY_DEFAULT, cancellable, function(initable, result) {
- try {
- initable.init_finish(result);
- asyncCallback(initable, null);
- } catch(e) {
- asyncCallback(null, e);
- }
- });
- else
- obj.init(cancellable);
- return obj;
- };
-}
function _newNodeInfo(constructor, value) {
@@ -234,14 +167,132 @@ function _newInterfaceInfo(value) {
return nodeInfo.interfaces[0];
}
-function _injectToMethod(klass, method, addition) {
- var previous = klass[method];
+const DBusProxyClass = new Lang.Class({
+ Name: 'DBusProxyClass',
+ Extends: GObject.Class,
- klass[method] = function() {
- addition.apply(this, arguments);
- return previous.apply(this, arguments);
+ _init: function(params) {
+ params.Extends = Gio.DBusProxy;
+
+ if (!params.Interface)
+ throw new TypeError('Interface must be specified in the declaration of a DBusProxyClass');
+ if (!(params.Interface instanceof Gio.DBusInterfaceInfo))
+ params.Interface = _newInterfaceInfo(params.Interface);
+
+ if (!('Flags' in params))
+ params.Flags = Gio.DBusProxyFlags.NONE;
+
+ let classParams = ['Interface', 'BusType', 'BusName', 'ObjectPath', 'Flags'];
+ for each (let param in classParams) {
+ // remove from params and move to the class
+ this[param] = params[param];
+ delete params[param];
+ }
+
+ params._init = function(params) {
+ let klass = this.constructor;
+
+ if (!params)
+ params = { };
+ klass._fillParameters(params);
+
+ let asyncCallback, cancellable = null;
+ if ('g_async_callback' in params) {
+ asyncCallback = params.g_async_callback;
+ delete params.g_async_callback;
+ }
+ if ('g_cancellable' in params) {
+ cancellable = params.g_cancellable;
+ delete params.g_cancellable;
+ }
+
+ this.parent(params);
+
+ if (asyncCallback) {
+ this.init_async(GLib.PRIORITY_DEFAULT, cancellable, function(initable, result) {
+ try {
+ initable.init_finish(result);
+ asyncCallback(initable, null);
+ } catch(e) {
+ asyncCallback(null, e);
+ }
+ });
+ } else {
+ this.init(cancellable);
+ }
+
+ // temporary hack, until we have proper GObject signals
+ this.connect('g-signal', klass._convertToNativeSignal);
+ }
+
+ // build the actual class
+ this.parent(params);
+
+ // add convenience methods
+ this._addDBusConvenience();
+ },
+
+ _fillParameters: function(params, bus, name, object) {
+ if (this.BusType !== undefined)
+ params.g_connection = Gio.bus_get_sync(this.BusType, null);
+ params.g_interface_name = this.Interface.name;
+ params.g_interface_info = this.Interface;
+ if (this.BusName !== undefined)
+ params.g_name = this.BusName;
+ if (this.ObjectPath !== undefined)
+ params.g_object_path = this.ObjectPath;
+ params.g_flags = this.Flags;
+ },
+
+ _addDBusConvenience: function() {
+ let info = this.Interface;
+
+ let i, methods = info.methods;
+ for (i = 0; i < methods.length; i++) {
+ var method = methods[i];
+ this.prototype[method.name + 'Remote'] = this._makeProxyMethod(methods[i], false);
+ this.prototype[method.name + 'Sync'] = this._makeProxyMethod(methods[i], true);
+ }
+
+ let properties = info.properties;
+ for (i = 0; i < properties.length; i++) {
+ let name = properties[i].name;
+ let signature = properties[i].signature;
+ let flags = properties[i].flags;
+ let getter = undefined, setter = undefined;
+
+ if (flags & Gio.DBusPropertyInfoFlags.READABLE) {
+ getter = function() {
+ return _propertyGetter.call(this, name);
+ };
+ }
+ if (flags & Gio.DBusPropertyInfoFlags.WRITABLE) {
+ setter = function(val) {
+ return _propertySetter.call(this, name, val, signature);
+ };
+ }
+
+ Lang.defineAccessorProperty(this.prototype, name, getter, setter);
+ }
+ },
+
+ _makeProxyMethod: function(method, sync) {
+ var i;
+ var name = method.name;
+ var inArgs = method.in_args;
+ var inSignature = [ ];
+ for (i = 0; i < inArgs.length; i++)
+ inSignature.push(inArgs[i].signature);
+
+ return this.wrapFunction(method, function() {
+ return _proxyInvoker.call(this, name, sync, inSignature, arguments);
+ });
+ },
+
+ _convertToNativeSignal: function(proxy, sender_name, signal_name, parameters) {
+ Signals._emit.call(proxy, signal_name, sender_name, parameters.deep_unpack());
}
-}
+});
function _wrapFunction(klass, method, addition) {
var previous = klass[method];
@@ -261,21 +312,23 @@ function _makeOutSignature(args) {
return ret + ')';
}
-function _wrapJSObject(interfaceInfo, jsObj) {
- var info;
- if (interfaceInfo instanceof Gio.DBusInterfaceInfo)
- info = interfaceInfo
- else
- info = Gio.DBusInterfaceInfo.new_for_xml(interfaceInfo);
- info.cache_build();
+const DBusImplementerBase = new Lang.Class({
+ Name: 'DBusImplementerBase',
+
+ _init: function() {
+ this._dbusImpl = new GjsDBus.Implementation({ g_interface_info: this.constructor.Interface });
- var impl = new GjsDBus.Implementation({ g_interface_info: info });
- impl.connect('handle-method-call', function(impl, method_name, parameters, invocation) {
+ this._dbusImpl.connect('handle-method-call', Lang.bind(this, this._handleMethodCall));
+ this._dbusImpl.connect('handle-property-get', Lang.bind(this, this._handlePropertyGet));
+ this._dbusImpl.connect('handle-property-set', Lang.bind(this, this._handlePropertySet));
+ },
+
+ _handleMethodCall: function(impl, method_name, parameters, invocation) {
// prefer a sync version if available
- if (jsObj[method_name]) {
- var retval;
+ if (this[method_name]) {
+ let retval;
try {
- retval = jsObj[method_name].apply(jsObj, parameters.deep_unpack());
+ retval = this[method_name].apply(this, parameters.deep_unpack());
} catch (e) {
if (e.name.indexOf('.') == -1) {
// likely to be a normal JS error
@@ -291,9 +344,11 @@ function _wrapJSObject(interfaceInfo, jsObj) {
try {
if (!(retval instanceof GLib.Variant)) {
// attemp packing according to out signature
- var methodInfo = info.lookup_method(method_name);
- var outArgs = methodInfo.out_args;
- var outSignature = _makeOutSignature(outArgs);
+ let klass = this.constructor;
+
+ let methodInfo = klass.Interface.lookup_method(method_name);
+ let outArgs = methodInfo.out_args;
+ let outSignature = _makeOutSignature(outArgs);
if (outArgs.length == 1) {
// if one arg, we don't require the handler wrapping it
// into an Array
@@ -307,28 +362,83 @@ function _wrapJSObject(interfaceInfo, jsObj) {
invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError',
"The return value from the method handler was not in the correct format");
}
- } else if (jsObj[method_name + 'Async']) {
- jsObj[method_name + 'Async'](parameters.deep_unpack(), invocation);
+ } else if (this[method_name + 'Async']) {
+ this[method_name + 'Async'](parameters.deep_unpack(), invocation);
} else {
log('Missing handler for DBus method ' + method_name);
invocation.return_dbus_error('org.gnome.gjs.NotImplementedError',
"Method ' + method_name + ' is not implemented");
}
- });
- impl.connect('handle-property-get', function(impl, property_name) {
- var propInfo = info.lookup_property(property_name);
- var jsval = jsObj[property_name];
+ },
+
+ _handlePropertyGet: function(impl, property_name) {
+ let klass = this.constructor;
+
+ let propInfo = klass.Interface.lookup_property(property_name);
+ let jsval = this[property_name];
if (jsval != undefined)
return GLib.Variant.new(propInfo.signature, jsval);
else
return null;
- });
- impl.connect('handle-property-set', function(impl, property_name, new_value) {
- jsObj[property_name] = new_value.deep_unpack();
- });
+ },
- return impl;
-}
+ _handlePropertySet: function(impl, property_name, new_value) {
+ this[property_name] = new_value.deep_unpack();
+ },
+
+ export: function(bus, path) {
+ this._dbusImpl.export(bus, path);
+ },
+
+ unexport: function() {
+ this._dbusImpl.unexport();
+ },
+
+ emit_signal: function(signal_name) {
+ let klass = this.constructor;
+
+ let signalInfo = klass.Interface.lookup_signal(signal_name);
+ let signalType = _makeOutSignature(signalInfo.args);
+
+ let argArray = Array.prototype.slice.call(arguments);
+ argArray.shift();
+
+ if (argArray.length == 0)
+ this._dbusImpl.emit_signal(signal_name, null);
+ else
+ this._dbusImpl.emit_signal(signal_name, GLib.Variant.new(signalType, argArray));
+ },
+
+ emit_property_changed: function(property_name, new_value) {
+ let klass = this.constructor;
+
+ let propertyInfo = klass.Inteface.lookup_property(property_name);
+ if (new_value != undefined)
+ new_value = GLib.Variant.new(propertyInfo.signature, new_value);
+
+ this._dbusImpl.emit_property_changed(property_name, new_value);
+ },
+});
+
+const DBusImplementerClass = new Lang.Class({
+ Name: 'DBusImplementerClass',
+ Extends: Lang.Class,
+
+ _init: function(params) {
+ if (!params.Interface)
+ throw new TypeError('Interface must be specified in the declaration of a DBusImplementerClass');
+ if (!(params.Interface instanceof Gio.DBusInterfaceInfo))
+ params.Interface = _newInterfaceInfo(params.Interface);
+ params.Interface.cache_build();
+
+ this.Interface = params.Interface;
+ delete params.Interface;
+
+ params.Extends = DBusImplementerBase;
+
+ this.parent(params);
+ }
+});
function _init() {
Gio = this;
@@ -369,19 +479,13 @@ function _init() {
return Gio.bus_unown_name(id);
};
- // This should be done inside a constructor, but it cannot currently
- _injectToMethod(Gio.DBusProxy.prototype, 'init', _addDBusConvenience);
- _injectToMethod(Gio.DBusProxy.prototype, 'init_async', _addDBusConvenience);
+ Gio.DBusProxyClass = DBusProxyClass;
Gio.DBusProxy.prototype.connectSignal = Signals._connect;
Gio.DBusProxy.prototype.disconnectSignal = Signals._disconnect;
- Gio.DBusProxy.makeProxyWrapper = _makeProxyWrapper;
-
// Some helpers
_wrapFunction(Gio.DBusNodeInfo, 'new_for_xml', _newNodeInfo);
Gio.DBusInterfaceInfo.new_for_xml = _newInterfaceInfo;
- // More or less...
- Gio.DBusExportedObject = GjsDBus.Implementation;
- Gio.DBusExportedObject.wrapJSObject = _wrapJSObject;
+ Gio.DBusImplementerClass = DBusImplementerClass;
}
diff --git a/test/js/testGDBus.js b/test/js/testGDBus.js
index e7f6495..0a503e9 100644
--- a/test/js/testGDBus.js
+++ b/test/js/testGDBus.js
@@ -1,6 +1,7 @@
// application/javascript;version=1.8
const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
+const Lang = imports.lang;
const Mainloop = imports.mainloop;
/* The methods list with their signatures.
@@ -20,7 +21,6 @@ var TestIface = <interface name="org.gnome.gjs.Test">
<arg type="a{sv}" direction="in"/>
<arg type="a{sv}" direction="out"/>
</method>
-<method name="thisDoesNotExist"/>
<method name="noInParameter">
<arg type="s" direction="out"/>
</method>
@@ -81,21 +81,19 @@ var TestIface = <interface name="org.gnome.gjs.Test">
<property name="PropReadWrite" type="v" access="readwrite" />
</interface>
-/* Test is the actual object exporting the dbus methods */
-function Test() {
- this._init();
-}
-
const PROP_READ_WRITE_INITIAL_VALUE = 58;
const PROP_WRITE_ONLY_INITIAL_VALUE = "Initial value";
-Test.prototype = {
- _init: function(){
+log('about to build a Test class');
+const Test = new Gio.DBusImplementerClass({
+ Name: 'Test',
+ Interface: TestIface,
+
+ _init: function() {
+ this.parent();
+
this._propWriteOnly = PROP_WRITE_ONLY_INITIAL_VALUE;
this._propReadWrite = PROP_READ_WRITE_INITIAL_VALUE;
-
- this._impl = Gio.DBusExportedObject.wrapJSObject(TestIface, this);
- this._impl.export(Gio.DBus.session, '/org/gnome/gjs/Test');
},
frobateStuff: function(args) {
@@ -114,10 +112,6 @@ Test.prototype = {
throw Error("Exception!");
},
- thisDoesNotExist: function () {
- /* We'll remove this later! */
- },
-
noInParameter: function() {
return "Yes!";
},
@@ -127,7 +121,7 @@ Test.prototype = {
},
emitSignal: function() {
- this._impl.emit_signal('signalFoo', GLib.Variant.new('(s)', [ "foobar" ]));
+ this.emit_signal('signalFoo', "foobar");
},
noReturnValue: function() {
@@ -195,7 +189,7 @@ Test.prototype = {
// variant
get PropReadWrite() {
- return GLib.Variant.new('s', this._propReadWrite.toString());
+ return GLib.Variant.new('u', this._propReadWrite);
},
set PropReadWrite(value) {
@@ -205,59 +199,90 @@ Test.prototype = {
structArray: function () {
return [[128, 123456], [42, 654321]];
}
-};
+});
var own_name_id;
+const ProxyClass = new Gio.DBusProxyClass({
+ Name: 'ProxyClass',
+ Interface: TestIface,
+});
+
+const SlimProxyClass = new Gio.DBusProxyClass({
+ Name: 'SlimProxyClass',
+ Interface: TestIface,
+ BusType: Gio.BusType.SESSION,
+ BusName: 'org.gnome.gjs.Test',
+ ObjectPath: '/org/gnome/gjs/Test'
+});
+
+var proxy, exporter;
+
function testExportStuff() {
- new Test();
+ exporter = new Test();
+ exporter.export(Gio.DBus.session, '/org/gnome/gjs/Test');
own_name_id = Gio.DBus.session.own_name('org.gnome.gjs.Test',
Gio.BusNameOwnerFlags.NONE,
- function(name) {
+ function(connection, name) {
log("Acquired name " + name);
Mainloop.quit('testGDBus');
},
- function(name) {
+ function(connection, name) {
log("Lost name " + name);
});
Mainloop.run('testGDBus');
}
-const ProxyClass = Gio.DBusProxy.makeProxyWrapper(TestIface);
-var proxy;
-
function testInitStuff() {
var theError;
- proxy = new ProxyClass(Gio.DBus.session,
- 'org.gnome.gjs.Test',
- '/org/gnome/gjs/Test',
- function (obj, error) {
+ proxy = new ProxyClass({ g_connection: Gio.DBus.session,
+ g_name: 'org.gnome.gjs.Test',
+ g_object_path: '/org/gnome/gjs/Test',
+ g_async_callback: function (obj, error) {
theError = error;
proxy = obj;
Mainloop.quit('testGDBus');
- });
+ } });
+
+ log(typeof(proxy._init) + " " + typeof(proxy._construct) + " " + typeof(proxy.frobateStuffRemote));
Mainloop.run('testGDBus');
+ assertNull(theError);
assertNotNull(proxy);
+}
+
+function testInitSlimStuff() {
+ var proxy, theError;
+ proxy = new SlimProxyClass({ g_async_callback: function (obj, error) {
+ theError = error;
+ proxy = obj;
+
+ Mainloop.quit('testGDBus');
+ }});
+
+ Mainloop.run('testGDBus');
+
assertNull(theError);
+ assertNotNull(proxy);
}
function testFrobateStuff() {
let theResult, theExcp;
proxy.frobateStuffRemote({}, function(result, excp) {
- [theResult] = result;
+ theResult = result;
theExcp = excp;
Mainloop.quit('testGDBus');
});
Mainloop.run('testGDBus');
- assertEquals("world", theResult.hello.deep_unpack());
+ assertNull(theExcp);
+ assertEquals("world", theResult[0].hello.deep_unpack());
}
/* excp must be exactly the exception thrown by the remote method
@@ -276,26 +301,6 @@ function testThrowException() {
assertNotNull(theExcp);
}
-/* We check that the exception in the answer is not null when we try to call
- * a method that does not exist */
-function testDoesNotExist() {
- let theResult, theExcp;
-
- /* First remove the method from the object! */
- delete Test.prototype.thisDoesNotExist;
-
- proxy.thisDoesNotExistRemote(function (result, excp) {
- theResult = result;
- theExcp = excp;
- Mainloop.quit('testGDBus');
- });
-
- Mainloop.run('testGDBus');
-
- assertNotNull(theExcp);
- assertNull(theResult);
-}
-
function testNonJsonFrobateStuff() {
let theResult, theExcp;
proxy.nonJsonFrobateStuffRemote(42, function(result, excp) {
@@ -556,11 +561,26 @@ function testDictSignatures() {
assertEquals(10.0, theResult['aDouble'].deep_unpack());
}
-function testFinalize() {
- // Not really needed, but if we don't cleanup
- // memory checking will complain
+function testProperties() {
+ let readonly = proxy.PropReadOnly;
+ assertEquals(true, readonly);
+ let readwrite = proxy.PropReadWrite;
+ assertTrue(readwrite instanceof GLib.Variant);
+ assertEquals(PROP_READ_WRITE_INITIAL_VALUE, readwrite.deep_unpack());
+
+ // we cannot test property sets, as those happen asynchronously
+}
+
+function testFinalize() {
+ // clean everything up, before we destroy the context
+ // (otherwise, gio will report a name owner changed signal
+ // in idle, which will be called in some other test)
Gio.DBus.session.unown_name(own_name_id);
+
+ proxy = exporter = null;
+ context = GLib.MainContext.default();
+ while (context.iteration(false));
}
gjstestRun();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]