sidorares / dbus-native

D-bus protocol client and server for node.js written in native javascript
Other
258 stars 93 forks source link

What's the syntax to return several values? #114

Open nschoe opened 8 years ago

nschoe commented 8 years ago

Currently trying to implement org.freedesktop.Notifications.

Inspecting by hand : gdbus introspect --session --dest org.freedesktop.Notifications --object-path /org/freedesktop/Notifications returns:

[...]
interface org.freedesktop.Notifications {
    methods:
      CloseNotification(in  u id);
      GetCapabilities(out as capabilities);
      GetServerInformation(out s name,
                           out s vendor,
                           out s version,
                           out s spec_version);
      Notify(in  s app_name,
             in  u replaces_id,
             in  s app_icon,
             in  s summary,
             in  s body,
             in  as actions,
             in  a{sv} hints,
             in  i expire_timeout,
             out u id);
    signals:
      ActionInvoked(u id,
                    s action_key);
      NotificationClosed(u id,
                         u reason);
    properties:
  };

So first step is to implement GetServerInformation. It has type: out s, out s, out s, out s.

From examples in examples/ I first thought that the method's type was defined as an array, with the last item being the return type and the N-1 first were input. But this can't be right because dbus obviously can return several values and because in /lib.bus.js:144 (see here) the resultSignature is hardcoded as element [1] of the array.

So I deduced that the correct format for method's type is [[array of input args], [array of output args]], smth like [['i', 'i'], ['s', 's', 's']] for a function that takes 2 integers as input and outputs 3 strings.

For the GetServerInformation method above, which takes no input but returns 4 strings, so I set the return type as: [[],['s', 's', 's', 's']].

Now it seems to work, because when I try to return an integer (5), I've got this error:

Error: message body does not match message signature. Body:[5], signature:s,s,s,s

We can see here that it complains because I try to return just 5, where the expected return type is s,s,s,s.

Now the question is: how can I actually return s,s,s,s?

In the body of my function, I have first tried:

return ['str1', 'str2', 'str3', 'str4'];

But I got error:

Error: message body does not match message signature. Body:[["str1","str2","str3","str4"]], signature:s,s,s,s

So it seems that the return arguments are "wrapped" in an array, but I don't know how to 'flatten' that array.

Is this an error in the library or am I misusing it? (In this case, it would be good to provide additional examples of a more complex use of the library, as of now, the three examples in examples/service/server2.js only shows examples of functions that take one argument and return one value.

sidorares commented 8 years ago

this definitely needs more documentation :(

can you post some of your code?

As far as I can remember, first element in array is signature for all input arguments and second element is result signature. So Notify handler and interface description might look like this (have not tested yet)

var iface = {
   //... other methods
   Notify: ['susssasa{sv}i', 'u' ]
   //... other methods
};

var NotifyService = {
  Notify: function(app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout) {
     // 
     return 12345; // out id
  }
}

// export 

Would be really helpful to have a tool to generate this from xml interface ( there is one for client side bot not for service - https://github.com/sidorares/node-dbus/blob/master/bin/dbus2js.js )

nschoe commented 8 years ago

@sidorares thanks for answering this fast. The problem I currently have is not for Notify because it returns only one value. My problem is for GetServerInformation which should return 4 strings. I don't understand how to achieve this.

I had this:

var notificationIface = {
                name: name,
                methods: {
                    Notify: [['s', 'u', 's', 's', 'as', 'a{sv}', 'i'], 'u'],
                    GetServerInformation: [[],['s', 's', 's', 's]]
                },
                signals: {

                },
                properties: {

                }
            };
var notification = {
                Notify: function (app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout) {
                    return 12345;
                },
                GetServerInformation: function () {
                    return ['Awesome Notification Server', 'example.org', '0.1', '1.2'];
                }
            };

And I got error:

Error: message body does not match message signature. Body:[["Awesome Notification Server","partnering.org","0.1","1.2"]], signature:s,s,s,s

Note: it did recognize signature as 4 strings: s,s,s,s

Then I tried to change GetServerInformation: [[],['s', 's', 's', 's]] to GetServerInformation: [[],'ssss']

And I got this time:

Error: message body does not match message signature. Body:[["Awesome Notification Server","partnering.org","0.1","1.2"]], signature:ssss

Note: this time, it said output was ssss, not s,s,s,s.

Anyway, this did not change: I still can't get GetServerInformation to return those 4 strings :/

Any idea?

sidorares commented 8 years ago

Can you console.log full message you receive when you invoke GetServerInformation? You could put something like console.log(msg) here - https://github.com/sidorares/node-dbus/blob/master/lib/bus.js#L99

sidorares commented 8 years ago

Basically function call is always two messages regardless of number of arguments: caller->service: message with call id, method name, iface name, object name, args signature + body if non empty. service->caller: response id = call id, response signature + body if non empty.

nschoe commented 8 years ago

So here is the full msg object I receive:

{ serial: 6,
  signature: '',
  path: '/org/freedesktop/Notifications',
  member: 'GetServerInformation',
  interface: 'org.freedesktop.Notifications',
  destination: ':1.269',
  sender: ':1.270',
  type: 1,
  flags: 0 }

As for your latest message, yes I think I got the dbus mechanism (albeit, still a bit new). What I don't know, is how the binding here handles response body. In my function getServerInformation, how should I return the 4 strings?

Currently I do return ['str1', 'str2', 'str3', 'str4'].

nschoe commented 8 years ago

Okay there is something weird... While checking my code, there seems to be a difference on this line: https://github.com/sidorares/node-dbus/blob/master/lib/bus.js#L180

Here, on my local machine, I have reply.body = [result]; instead of reply.body = result; which explains the fact that in the error messages, there is always a pair of brackets.

I do not understand how these brackets got here, and even worse: they don't appear on git diff. Anyway, I remove it, now I've got a different error:

Error: Expected string or buffer argument, got ["s","s","s","s"] of type 'g'

What does this mean?

sidorares commented 8 years ago

looks like it's a problem with the library.

Here I wrap result with extra [], assuming that there are only 1 value:

https://github.com/sidorares/node-dbus/blob/master/lib/bus.js#L152

And here body is set as is to the value you return from handler:

https://github.com/sidorares/node-dbus/blob/master/lib/bus.js#L180

As a work around you might use second approach, e.i use setMethodCallHandler instead of exportInterface.

sidorares commented 8 years ago

you beat me :)

nschoe commented 8 years ago

@sidorares OMg I understand why I made the confusion: when I checked your code, it was line 180. I then checked my code on line 180 and it was the same line, without the brackets.

Actually, for debugging purpose, I had added comments and these matched EXACTLY to line 180.

But the truth is: I had the same version.

So what should I do? You suggest I don't use exportInterface? But then how do I "export" the interface? How do I implement this Notifications interface?

Thanks for your reactivity!

sidorares commented 8 years ago

There is a lot of confusion when people interpret signature values depending on what you need. In general, signature is always a struct (but maybe with one element):

s  -> [ 'string' ]
ss -> [ 'string1', 'string2' ]
nschoe commented 8 years ago

Yes, I have been browsing all issued (opened and closed) and I saw that this was something of a recurrent problem. So I tried, but if GetServerInformation, if I return [['str1', 'str2', 'str3', 'str4']]; (with extra bracket) instead of return ['str1', 'str2', 'str3', 'str4'];.

It doesn't change a thing: the error message is the same, with one extra bracket:

Error: message body does not match message signature. Body:[[['str1', 'str2', 'str3', 'str4']]], signature:s,s,s,s

What's the correct syntax, in the body of GetServerInformation() to return those for strings? Should I return a structure? If yes, what's the form?

I'm a bit lost :-)

sidorares commented 8 years ago

you can use setMethodCallHandler - https://github.com/sidorares/node-dbus/blob/master/lib/bus.js#L190 It allows you to set handlers one by one, not the whole interface

I'll need to think a good way of handling results which is backwards compatible and not unexpected.

Proposed solution: if result signature is single element struct, wrap it into struct ( example: interface is 's' and you would return 'string';. If not, don't wrap ( interface is 'ssi' and you return ['test', 'foo', 42]; )

sidorares commented 8 years ago

as I mentioned, setMethodCallHandler behaviour re return value is different from exportInterface ( which is bad ;( ). First one serialises what you return as is ( so if the result is single value you need to wrap as a struct - return [123]; for 'i' handler )

nschoe commented 8 years ago

@sidorares Okay, I will try to setMethodCallHandler. But if I do that, the interface won't be properly implemented, will it?

(I will try this of course).

Does it work if I first use exportInterface and then, call setMethodCallHandler, just on the function(s) for which it is necessary?

sidorares commented 8 years ago

interface won't be properly implemented

form "handling" point of view this is identical, I have to look more closely at the code to check if introspection is generated correctly for setMethodCallHandler methods

sidorares commented 8 years ago

yes, you should be able to mix them

nschoe commented 8 years ago

@sidorares I've tried to use setMethodCallHandler but I cannot get it to work.

I've looked at the function's type signature, and the iface parameters seems unclear, so I tried to set it to:

But neither works, every time, when I call the GetServerInformation, I get error

console.log ('\nfunc.type: ' + func.type);
TypeError: Cannot read property 'type' of undefined

Besides, in the bus.js file, I display the content of iface[1], which normally contains the functions implementation (in my case, Notify and GetServerInformation) ,but this time, I only get Notify.

So either setMethodCallHandler doesn't work as intended, or maybe I'm missing a step :-)

Any idea?

Thanks for your support!

nschoe commented 8 years ago

Okay, so I've scouted through the code, and compared when you call mangle() on msg, it seems that, for calling setMethodCallHandler, the iface should be the name of the interface, so org.freedesktop.Notifications.

I re-tried that, but then, when calling the GetServerInformation method, it fails with TypeError: Cannot read property 'type' of undefined when I try to display func.

nschoe commented 8 years ago

@sidorares any news regarding the problem?

nschoe commented 8 years ago

@sidorares for information, when I use the setMethodCallHandler and then later try to call the said function, I fall into this condition , which means that func is null, which means it doesn't have the method.

It seems normal to me, because when calling setMethodCallHandler it simply adds the key to the methodCallHandlers array as shown here, but nowhere is this array checked when calling a function. So I wonder if I am missing something or if you simply forgot to check this array ?