[gjs/wip/gobj-kitchen-sink: 13/20] GDBus: introduce new convenience wrappers



commit d0372fc032add5a0c1a4cc9f794a0383f5a33137
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]