[jscore] Fix object path handling in signals (now you can filter with the path finally!)
[browser-dbus-bridge.git] / jscorebus / jscorebus-signal.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
29 /* Private data for signals */
30 typedef struct _SignalPrivate
31 {
32   char *interface;
33   char *signal_name;
34   char *sender;
35   char *object_path;
36
37   DBusConnection *connection;
38   char *match_rule;
39
40   JSGlobalContextRef context;
41   JSObjectRef this;
42   JSObjectRef onemit;
43   gboolean enabled;
44 } SignalPrivate;
45
46 /* Utility functions */
47 static
48 JSClassRef get_class ();
49
50 static
51 void add_match_and_handler(SignalPrivate *priv);
52 static
53 void remove_match_and_handler(SignalPrivate *priv);
54
55 /* JSCore methods */
56 static
57 void signal_finalize(JSObjectRef object);
58
59 static
60 bool signal_set_property(JSContextRef context,
61                          JSObjectRef object,
62                          JSStringRef propertyName,
63                          JSValueRef value,
64                          JSValueRef* exception);
65
66 /* We keep a hash table to track the signals callbacks */
67 static GHashTable *signal_hash;
68
69 /* The Signal Class */
70 static const
71 JSClassDefinition signal_jsclass_def =
72 {
73   0,
74   kJSClassAttributeNone,
75   "DBusSignal",
76   NULL,
77
78   NULL,
79   NULL,
80   
81   NULL, /* Initialize */
82   signal_finalize,
83   
84   NULL,
85   NULL,
86   signal_set_property, /* SetProperty */
87   NULL,
88   NULL,
89   
90   NULL, /* CallAsFunction */
91   NULL, /* Constructor */
92   NULL,
93   NULL
94 };
95
96 /*** JSCore methods */
97
98 static
99 void signal_finalize(JSObjectRef object)
100 {
101   SignalPrivate *priv = (SignalPrivate *)JSObjectGetPrivate(object);
102
103   if (priv != NULL)
104   {
105     remove_match_and_handler(priv);
106     
107     g_free(priv->object_path);
108     g_free(priv->signal_name);
109     g_free(priv->interface);
110     g_free(priv->sender);
111     g_free(priv->match_rule);
112     dbus_connection_unref(priv->connection);
113
114     priv->this = NULL;
115     priv->onemit = NULL;
116     priv->context = NULL;
117     
118     g_free(priv);
119     priv = NULL;
120   }
121 }
122
123 static
124 bool signal_set_property(JSContextRef context,
125                          JSObjectRef object,
126                          JSStringRef propertyName,
127                          JSValueRef value,
128                          JSValueRef* exception)
129 {
130   SignalPrivate *priv;
131
132   priv = (SignalPrivate *)JSObjectGetPrivate(object);
133   g_assert(priv != NULL);
134
135   if (JSStringIsEqualToUTF8CString(propertyName, "enabled"))
136   {
137     /* We don't enable unless we have a callback */
138     if (priv->onemit == NULL)
139     {
140       /* TODO: Throw exception */
141       return TRUE;
142     }
143     if (JSValueIsBoolean(context, value))
144     {
145       priv->enabled = JSValueToBoolean(context, value);
146       if (priv->enabled)
147       {
148         add_match_and_handler(priv);
149       }
150       return TRUE;
151     }
152     
153     /* TODO: set exception */
154     g_warning("Tried to set a non-boolean to 'enabled'");
155     return TRUE;
156   } else if (JSStringIsEqualToUTF8CString(propertyName, "onemit")) {
157     priv->onemit = function_from_jsvalue(context, value, exception);
158
159     /* If the callback function goes away, we need to disable the signal,
160       * remove match and remove our entry from the handlers
161       */
162     if (priv->onemit == NULL)
163     {
164       remove_match_and_handler(priv);
165     }
166     return TRUE;
167   }
168
169   return FALSE;
170 }
171
172 /*** Utility functions */
173
174 static
175 JSClassRef get_class ()
176 {
177   JSClassRef jsclass = (JSClassRef)jsclass_lookup(&signal_jsclass_def);
178   
179   if (G_LIKELY(jsclass != NULL))
180   {
181     return jsclass;
182   }
183   
184   jsclassdef_insert("DBusSignal", &signal_jsclass_def);
185   jsclass = (JSClassRef)jsclass_lookup(&signal_jsclass_def);
186
187   g_assert(jsclass != NULL);
188
189   return jsclass;
190 }
191
192 static
193 void add_match_and_handler(SignalPrivate *priv)
194 {
195   char *signal_str;
196   GPtrArray *handlers;
197
198   dbus_bus_add_match(priv->connection, priv->match_rule, NULL);
199
200   signal_str = g_strdup_printf("%s.%s", priv->interface, priv->signal_name);
201   handlers = g_hash_table_lookup(signal_hash, signal_str);
202
203   if (handlers == NULL)
204   {
205     handlers = g_ptr_array_new();
206     g_hash_table_insert(signal_hash, signal_str, handlers);
207   } else {
208     g_free(signal_str);
209   }
210
211   g_ptr_array_add(handlers, priv);
212 }
213
214 static
215 void remove_match_and_handler(SignalPrivate *priv)
216 {
217   char *signal_str;
218   GPtrArray *handlers;
219
220   signal_str = g_strdup_printf("%s.%s", priv->interface, priv->signal_name);
221
222   handlers = g_hash_table_lookup(signal_hash, signal_str);
223
224   if (handlers != NULL)
225   {
226     g_ptr_array_remove(handlers, priv);
227     if (handlers->len == 0)
228     {
229       g_hash_table_remove(signal_hash, signal_str);
230       g_ptr_array_free(handlers, TRUE);
231     }
232   }
233
234   g_free(signal_str);
235
236   dbus_bus_remove_match(priv->connection, priv->match_rule, NULL);
237
238 }
239
240 static
241 void call_onemit(SignalPrivate *priv,
242                  DBusMessage *message)
243 {
244
245   if (priv->enabled == FALSE
246    || priv->onemit == NULL)
247   {
248     return;
249   }
250
251   /* If the signal was created with a sender, we need to check for it
252    * and not deliver if it does not match
253    */
254   if (priv->sender != NULL)
255   {
256     if (!dbus_message_has_sender(message, priv->sender))
257     {
258       return;
259     }
260   }
261   /* Ditto for object paths */
262   if (priv->object_path != NULL)
263   {
264     if (!dbus_message_has_path(message, priv->object_path))
265     {
266       return;
267     }
268   }
269
270   call_function_with_message_args(priv->context, priv->this, priv->onemit, message);
271 }
272
273 static
274 DBusHandlerResult signal_filter(DBusConnection *connection,
275                                 DBusMessage *message,
276                                 void *user_data)
277 {
278   char *signal_str;
279   GPtrArray *handlers;
280
281   if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
282   {
283     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
284   }
285
286   signal_str = g_strdup_printf("%s.%s",
287                                dbus_message_get_interface(message),
288                                dbus_message_get_member(message));
289
290   handlers = g_hash_table_lookup(signal_hash, signal_str);
291   g_free(signal_str);
292   
293   if (handlers == NULL || handlers->len == 0)
294   {
295     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
296   }
297
298   g_ptr_array_foreach(handlers, (GFunc)call_onemit, message);
299
300   return DBUS_HANDLER_RESULT_HANDLED;
301 }
302
303 /*** Public API */
304
305 /* NOTE: Takes ownership of the arguments! */
306 JSObjectRef jscorebus_create_signal (JSGlobalContextRef context,
307                                      DBusConnection *connection,
308                                      char *interface,
309                                      char *signal_name,
310                                      char *sender,
311                                      char *object_path,
312                                      JSObjectRef thisObject,
313                                      JSValueRef* exception)
314 {
315   int i;
316   char **matchv;
317   char *signal_str;
318   GPtrArray *handlers;
319   SignalPrivate *priv = NULL;
320   static gboolean filter_added = FALSE;
321
322   g_return_val_if_fail(interface != NULL
323                        && signal_name != NULL,
324                        NULL);
325
326   priv = g_new0(SignalPrivate, 1);
327
328   priv->object_path = object_path;
329   priv->interface = interface;
330   priv->signal_name = signal_name;
331   priv->sender = sender;
332
333   priv->enabled = false;
334
335   priv->connection = dbus_connection_ref(connection);
336   
337   priv->context = context;
338   priv->this = thisObject;
339
340   /* If we don't already have a filter function for the signals, add one */
341   if (G_UNLIKELY(!filter_added))
342   {
343     signal_hash = g_hash_table_new(g_str_hash, g_str_equal);
344     dbus_connection_add_filter(connection, signal_filter, NULL, NULL);
345     filter_added = TRUE;
346   }
347
348   /* Add the match rule for the signal */
349   matchv = g_new0(char*, 4);
350   i = 0;
351   matchv[i++] = g_strdup_printf("type=signal,interface=%s,member=%s",
352                                 priv->interface, priv->signal_name);
353   if (priv->sender != NULL)
354   {
355     matchv[i++] = g_strdup_printf("sender=%s", priv->sender);
356   }
357   if (priv->object_path != NULL)
358   {
359     matchv[i++] = g_strdup_printf("path=%s", priv->object_path);
360   }
361
362   priv->match_rule = g_strjoinv(",", matchv);
363   g_strfreev(matchv);
364   
365   return JSObjectMake(context, get_class(), priv);
366 }
367