Import the WebKit version
[browser-dbus-bridge.git] / jscorebus / jscorebus-method.c
1 /**
2  * Browser D-Bus Bridge, JavaScriptCore version
3  *
4  * Copyright © 2008 Movial Creative Technologies Inc
5  *  Contact: Movial Creative Technologies Inc, <info@movial.com>
6  *  Authors: Kalle Vahlman, <kalle.vahlman@movial.com>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
20  *
21  */
22
23 #include <glib.h>
24 #include <dbus/dbus.h>
25 #include <JavaScriptCore/JavaScript.h>
26
27 #include "jscorebus-marshal.h"
28 #include "jscorebus-method.h"
29
30 /* Private data for methods */
31 typedef struct _MethodPrivate
32 {
33   char *destination;
34   char *object_path;
35   char *method_name;
36   char *interface;
37   char *signature;
38
39   DBusConnection *connection;
40   DBusMessage *message;
41   DBusPendingCall *pending_reply;
42
43   JSGlobalContextRef context;
44   JSObjectRef this;
45   JSObjectRef onreply;
46   JSObjectRef onerror;
47   gboolean async;
48 } MethodPrivate;
49
50 /* Utility functions */
51 static
52 JSClassRef get_class ();
53
54 static
55 JSValueRef call_sync(JSContextRef context, MethodPrivate *priv);
56 static
57 JSValueRef call_async(JSContextRef context, MethodPrivate *priv);
58 static
59 void call_onreply(JSContextRef context,
60                   MethodPrivate *priv,
61                   DBusMessage *message);
62 static
63 void call_onerror(JSContextRef context,
64                   MethodPrivate *priv,
65                   DBusMessage *message);
66
67 /* JSCore methods */
68 static
69 void method_finalize(JSObjectRef object);
70
71 static
72 bool method_set_property(JSContextRef context,
73                          JSObjectRef object,
74                          JSStringRef propertyName,
75                          JSValueRef value,
76                          JSValueRef* exception);
77
78 static
79 JSValueRef method_call (JSContextRef context,
80                         JSObjectRef function,
81                         JSObjectRef thisObject,
82                         size_t argumentCount,
83                         const JSValueRef arguments[],
84                         JSValueRef *exception);
85
86 /* The Method Class */
87 static const
88 JSClassDefinition method_jsclass_def =
89 {
90   0,
91   kJSClassAttributeNone,
92   "DBusMethod",
93   NULL,
94
95   NULL,
96   NULL,
97   
98   NULL, /* Initialize */
99   method_finalize,
100   
101   NULL,
102   NULL,
103   method_set_property, /* SetProperty */
104   NULL,
105   NULL,
106   
107   method_call, /* CallAsFunction */
108   NULL, /* Constructor */
109   NULL,
110   NULL
111 };
112
113 /*** JSCore methods */
114
115 static
116 void method_finalize(JSObjectRef object)
117 {
118   MethodPrivate *priv = (MethodPrivate *)JSObjectGetPrivate(object);
119
120   if (priv != NULL)
121   {
122
123     if (priv->pending_reply != NULL)
124     {
125       dbus_pending_call_cancel(priv->pending_reply);
126     }
127   
128     g_free(priv->destination);
129     g_free(priv->object_path);
130     g_free(priv->method_name);
131     g_free(priv->interface);
132     g_free(priv->signature);
133     
134     dbus_connection_unref(priv->connection);
135
136     priv->this = NULL;
137     priv->onreply = NULL;
138     priv->onerror = NULL;
139     priv->context = NULL;
140     
141     g_free(priv);
142     priv = NULL;
143   }
144 }
145
146 static
147 bool method_set_property(JSContextRef context,
148                          JSObjectRef object,
149                          JSStringRef propertyName,
150                          JSValueRef value,
151                          JSValueRef* exception)
152 {
153   MethodPrivate *priv;
154
155   priv = (MethodPrivate *)JSObjectGetPrivate(object);
156   g_assert(priv != NULL);
157
158   if (JSStringIsEqualToUTF8CString(propertyName, "async"))
159   {
160     if (JSValueIsBoolean(context, value))
161     { 
162       priv->async = JSValueToBoolean(context, value);
163       return TRUE;
164     }
165     
166     /* TODO: set exception */
167     g_warning("Tried to set a non-boolean to 'async'");
168     return TRUE;
169   } else if (JSStringIsEqualToUTF8CString(propertyName, "onreply")) {
170     priv->onreply = function_from_jsvalue(context, value, exception);
171     return TRUE;
172   } else if (JSStringIsEqualToUTF8CString(propertyName, "onerror")) {
173     priv->onerror = function_from_jsvalue(context, value, exception);
174     return TRUE;
175   }
176
177   return FALSE;
178 }
179
180 static
181 JSValueRef method_call (JSContextRef context,
182                         JSObjectRef function,
183                         JSObjectRef thisObject,
184                         size_t argumentCount,
185                         const JSValueRef arguments[],
186                         JSValueRef *exception)
187 {
188   int i;
189   MethodPrivate *priv;
190   DBusMessageIter iter;
191   JSValueRef ret;
192         
193   priv = (MethodPrivate *)JSObjectGetPrivate(function);
194   g_assert(priv != NULL);
195
196   priv->message = dbus_message_new_method_call(priv->destination,
197                                                priv->object_path,
198                                                priv->interface,
199                                                priv->method_name);
200   
201   if (argumentCount > 0)
202   {
203     /* Push arguments to the message */
204     dbus_message_iter_init_append (priv->message, &iter);
205     if (!jsvalue_array_append_to_message_iter(context,
206                                               arguments, argumentCount,
207                                               &iter, priv->signature))
208     {
209       dbus_message_unref(priv->message);
210       priv->message = NULL;
211     }
212   }
213   
214   if (priv->async)
215   {
216     ret = call_async(context, priv);
217   } else {
218     ret = call_sync(context, priv);
219   }
220
221   if (priv->message != NULL)
222   {
223     dbus_message_unref(priv->message);
224   }
225   priv->message = NULL;
226
227   return ret;
228 }
229
230 /*** Utility functions */
231
232 static
233 JSClassRef get_class ()
234 {
235   JSClassRef jsclass = (JSClassRef)jsclass_lookup(&method_jsclass_def);
236   
237   if (G_LIKELY(jsclass != NULL))
238   {
239     return jsclass;
240   }
241   
242   jsclassdef_insert("DBusMethod", &method_jsclass_def);
243   jsclass = (JSClassRef)jsclass_lookup(&method_jsclass_def);
244
245   g_assert(jsclass != NULL);
246
247   return jsclass;
248 }
249
250 static
251 JSValueRef call_sync(JSContextRef context, MethodPrivate *priv)
252 {
253   /**
254    * Set up reply callback from the method.onReply property if available and
255    * send the message
256    */
257
258   if (priv->message == NULL)
259   {
260     call_onerror(context, priv, NULL);
261     return JSValueMakeUndefined(context);
262   }
263
264   if (priv->onreply != NULL)
265   {
266     DBusMessage *reply_message;
267     
268     reply_message = dbus_connection_send_with_reply_and_block(
269                       priv->connection,
270                       priv->message, -1, NULL);
271     if (reply_message != NULL)
272     {
273       if (dbus_message_get_type(reply_message) == DBUS_MESSAGE_TYPE_ERROR)
274       {
275         call_onerror(context, priv, reply_message);
276       } else if (dbus_message_get_type(reply_message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
277         call_onreply(context, priv, reply_message);
278       } else {
279         g_warning("Unknown reply!");
280       }
281     } else {
282       // TODO: set exception
283       g_warning("Failed to send message to %s", priv->destination);
284       call_onerror(context, priv, NULL);
285     }
286   } else {
287     if (!dbus_connection_send(priv->connection, priv->message, NULL))
288     {
289       // TODO: set exception
290       g_warning("Failed to send message to %s", priv->destination);
291       call_onerror(context, priv, NULL);
292     }
293   }  
294
295   return JSValueMakeUndefined(context);
296 }
297
298 static
299 void pending_call_notify(DBusPendingCall *pending,
300                          void *user_data)
301 {
302   DBusMessage *reply_message;
303   MethodPrivate *priv;
304   
305   g_assert(user_data != NULL);
306   
307   priv = (MethodPrivate *)user_data;
308   g_assert(priv->pending_reply != NULL);
309
310   if (pending == NULL)
311   {
312     /* Disconnected!
313      * TODO: How do we handle these?
314      */
315     g_warning("Disconnected from the bus!");
316     priv->pending_reply = NULL;
317     return;
318   }
319  
320   priv->pending_reply = NULL;
321   reply_message = dbus_pending_call_steal_reply(pending);
322
323   if (dbus_message_get_type(reply_message) == DBUS_MESSAGE_TYPE_ERROR)
324   {
325     call_onerror(priv->context, priv, reply_message);
326   } else if (dbus_message_get_type(reply_message) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
327     call_onreply(priv->context, priv, reply_message);
328   } else {
329     g_warning("Unknown reply!");
330   }
331
332 }
333
334 static
335 JSValueRef call_async(JSContextRef context, MethodPrivate *priv)
336 {
337         dbus_uint32_t serial = 0;
338
339   if (priv->message == NULL)
340   {
341     call_onerror(context, priv, NULL);
342   }
343
344   if (priv->onreply != NULL)
345   {
346     if (dbus_connection_send_with_reply(
347           priv->connection,
348           priv->message, &priv->pending_reply, -1))
349     {
350       dbus_pending_call_set_notify(priv->pending_reply, pending_call_notify,
351                                    priv, NULL);
352     } else {
353       // TODO: set exception
354       g_warning("Failed to send message to %s", priv->destination);
355       call_onerror(context, priv, NULL);
356     }
357   } else {
358     dbus_message_set_no_reply(priv->message, TRUE);
359
360     if (!dbus_connection_send(priv->connection, priv->message, &serial))
361     {
362       // TODO: set exception
363       call_onerror(context, priv, NULL);
364     }
365   }
366   
367   return JSValueMakeUndefined(context);
368 }
369
370 static
371 void call_onreply(JSContextRef context,
372                   MethodPrivate *priv,
373                   DBusMessage *message)
374 {
375   if (priv->onreply == NULL)
376   {
377     return;
378   }
379
380   call_function_with_message_args(context, priv->this, priv->onreply, message);
381 }
382
383 static
384 void call_onerror(JSContextRef context,
385                   MethodPrivate *priv,
386                   DBusMessage *message)
387 {
388   if (priv->onerror == NULL)
389   {
390     return;
391   }
392
393   if (message == NULL)
394   {
395     /* We couldn't send the message */
396     JSStringRef name = JSStringCreateWithUTF8CString("MessageError");
397     JSStringRef str = JSStringCreateWithUTF8CString("Could not send message");
398     JSValueRef *args = g_new(JSValueRef, 2);
399     
400     args[0] = JSValueMakeString(context, name);
401     args[1] = JSValueMakeString(context, str);
402     
403     JSObjectCallAsFunction(context, priv->onerror, priv->this,
404                            2, args, NULL);
405     JSStringRelease(name);
406     JSStringRelease(str);
407     return;
408   }
409
410   call_function_with_message_args(context, priv->this, priv->onerror, message);
411 }
412
413 /*** Public API */
414
415 /* NOTE: Takes ownership of the arguments! */
416 JSObjectRef jscorebus_create_method (JSGlobalContextRef context,
417                                      DBusConnection *connection,
418                                      char *destination,
419                                      char *object_path,
420                                      char *method_name,
421                                      char *interface,
422                                      char *signature,
423                                      JSObjectRef thisObject,
424                                      JSValueRef* exception)
425 {
426   MethodPrivate *priv = NULL;
427
428   g_return_val_if_fail(destination != NULL
429                        && object_path != NULL
430                        && method_name != NULL,
431                        NULL);
432
433   priv = g_new0(MethodPrivate, 1);
434
435   /* TODO: Maybe these should be JSStrings after all, to avoid copies? */
436   priv->destination = destination;
437   priv->object_path = object_path;
438   priv->method_name = method_name;
439
440   priv->interface = interface;
441   priv->signature = signature;
442
443   priv->async = true;
444
445   priv->connection = dbus_connection_ref(connection);
446   
447   priv->context = context;
448
449   priv->this = thisObject;
450
451   return JSObjectMake(context, get_class(), priv);
452 }