Import the XPCOM version
[browser-dbus-bridge.git] / xpcom-dbusservice / DBusService.cpp
1 /**
2  * Browser D-Bus Bridge, XPCOM version
3  *
4  * Copyright © 2008 Movial Creative Technologies Inc
5  *  Contact: Movial Creative Technologies Inc, <info@movial.com>
6  *  Authors: Lauri Mylläri, <lauri.myllari@movial.fi>
7  *           Kalle Vahlman, <kalle.vahlman@movial.com>
8  *
9  * The contents of this file are subject to the Mozilla Public License
10  * Version 1.1 (the "License"); you may not use this file except in
11  * compliance with the License. You may obtain a copy of the License at
12  * http://www.mozilla.org/MPL/
13  *
14  * Software distributed under the License is distributed on an "AS IS"
15  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
16  * License for the specific language governing rights and limitations
17  * under the License.
18  *
19  * The Original Code is the Browser D-Bus Bridge, XPCOM version.
20  *
21  * The Initial Developer of the Original Code is Movial Creative Technologies
22  * Inc. Portions created by Initial Developer are Copyright (C) 2008
23  * Movial Creative Technologies Inc. All Rights Reserved.
24  *
25  */
26
27 #include <stdio.h>
28 #include <glib.h>
29 #include <dbus/dbus-glib-lowlevel.h>
30 #include <dbus/dbus-glib.h>
31
32
33 #include "nsIGenericFactory.h"
34 #include "nsIInterfaceRequestorUtils.h"
35 #include "nsComponentManagerUtils.h"
36
37 #include "nsEmbedString.h"
38 #include "nsIMutableArray.h"
39 #include "nsArrayUtils.h"
40
41 #include "IDBusService.h"
42
43 #include "DBusService.h"
44 #include "DBusMethod.h"
45 #include "DBusSignal.h"
46 #include "DBusMarshaling.h"
47
48 #include "bdb-debug.h"
49
50 //
51 // DBusService implementation
52 //
53
54 static
55 DBusHandlerResult _signal_filter(DBusConnection *connection,
56                                  DBusMessage *message,
57                                  void *user_data);
58
59 static DBusService *gDBusService = nsnull;
60
61 NS_IMPL_ISUPPORTS1(DBusService, IDBusService);
62
63 DBusService::DBusService() :
64     mSystemBus(nsnull),
65     mSessionBus(nsnull),
66     mSystemBusHasFilter(PR_FALSE),
67     mSessionBusHasFilter(PR_FALSE)
68 {
69     BDBLOG(("DBusService::DBusService()\n"));
70     mSystemBusSignalObservers.Init();
71     mSessionBusSignalObservers.Init();
72 }
73
74 DBusService::~DBusService()
75 {
76     BDBLOG(("DBusService::~DBusService()\n"));
77     /* FIXME - check if connections need to be released */
78 }
79
80 NS_IMETHODIMP
81 DBusService::GetSignal(PRUint32 aBusType,
82                        const nsACString& aInterfaceName,
83                        const nsACString& aSignalName,
84                        const nsACString& aSender,
85                        const nsACString& aObjectPath,
86                        IDBusSignal **_retval)
87 {
88     nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *signalObservers = nsnull;
89     *_retval = nsnull;
90
91     GetConnection(aBusType);
92
93     if (aBusType == SYSTEM)
94     {
95         if (!mSystemBusHasFilter)
96         {
97             signalObservers = &mSystemBusSignalObservers;
98             mSystemBusHasFilter = PR_TRUE;
99         }
100     }
101     else if (aBusType == SESSION)
102     {
103         if (!mSessionBusHasFilter)
104         {
105             signalObservers = &mSessionBusSignalObservers;
106             mSessionBusHasFilter = PR_TRUE;
107         }
108     }
109     else
110     {
111         BDBLOG(("DBusService::GetSignal(): unknown bus type %d\n", aBusType));
112         return NS_ERROR_ILLEGAL_VALUE;
113     }
114
115     /* add filter only once for each connection */
116     if (signalObservers)
117         dbus_connection_add_filter(GetConnection(aBusType),
118                                    _signal_filter,
119                                    signalObservers,
120                                    nsnull);
121
122     IDBusSignal *signal = new DBusSignal(this,
123                                          aBusType,
124                                          aInterfaceName,
125                                          aSignalName,
126                                          aSender,
127                                          aObjectPath);
128
129     NS_ENSURE_TRUE(signal, NS_ERROR_OUT_OF_MEMORY);
130
131     NS_ADDREF(*_retval = signal);
132
133     return NS_OK;
134 }
135
136 NS_IMETHODIMP
137 DBusService::GetMethod(PRUint32 aBusType,
138                        const nsACString& aDestination,
139                        const nsACString& aObjectPath,
140                        const nsACString& aMethodName,
141                        const nsACString& aInterfaceName,
142                        const nsACString& aSignature,
143                        IDBusMethod **_retval)
144 {
145     *_retval = nsnull;
146
147     if (!GetConnection(aBusType))
148     {
149         BDBLOG(("DBusService::GetMethod()): invalid bus type %d\n",
150                aBusType));
151         return NS_ERROR_ILLEGAL_VALUE;
152     }
153
154     if (!dbus_signature_validate(PromiseFlatCString(aSignature).get(), nsnull))
155     {
156         BDBLOG(("DBusService::GetMethod()): invalid method signature '%s'\n",
157                PromiseFlatCString(aSignature).get()));
158         return NS_ERROR_ILLEGAL_VALUE;
159     }
160
161     IDBusMethod *method = new DBusMethod(this,
162                                          aBusType,
163                                          aDestination,
164                                          aObjectPath,
165                                          aMethodName,
166                                          aInterfaceName,
167                                          aSignature);
168
169     NS_ENSURE_TRUE(method, NS_ERROR_OUT_OF_MEMORY);
170
171     NS_ADDREF(*_retval = method);
172
173     return NS_OK;
174 }
175
176 DBusPendingCall *DBusService::SendWithReply(PRUint32 aConnType,
177                                             DBusMessage *aMessage,
178                                             PRUint32 aTimeout)
179 {
180     DBusPendingCall *retval = nsnull;
181     DBusConnection *conn = GetConnection(aConnType);
182
183     if (!conn)
184         return nsnull;
185
186     if (!dbus_connection_send_with_reply(conn,
187                                          aMessage,
188                                          &retval,
189                                          aTimeout))
190         return nsnull;
191
192     return retval;
193 }
194
195 DBusMessage *DBusService::SendWithReplyAndBlock(PRUint32 aConnType,
196                                                 DBusMessage *aMessage,
197                                                 PRUint32 aTimeout,
198                                                 DBusError *aError)
199 {
200     DBusConnection *conn = GetConnection(aConnType);
201
202     if (!conn)
203         return nsnull;
204
205     return dbus_connection_send_with_reply_and_block(conn,
206                                                      aMessage,
207                                                      aTimeout,
208                                                      aError);
209 }
210
211 static
212 DBusHandlerResult _signal_filter(DBusConnection *connection,
213                                  DBusMessage *message,
214                                  void *user_data)
215 {
216     if (dbus_message_get_type(message) != DBUS_MESSAGE_TYPE_SIGNAL)
217     {
218         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
219     }
220
221     nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *observerHash =
222         (nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *) user_data;
223
224     printf("_signal_filter: %s.%s\n",
225            dbus_message_get_interface(message),
226            dbus_message_get_member(message));
227
228     nsCAutoString observerKey;
229
230     observerKey.Assign(dbus_message_get_interface(message));
231     observerKey.Append(NS_LITERAL_CSTRING("."));
232     observerKey.Append(dbus_message_get_member(message));
233
234     printf("  observerKey: '%s'\n",
235            PromiseFlatCString(observerKey).get());
236
237     nsTArray<nsWeakPtr> *observerList = nsnull;
238
239     observerHash->Get(observerKey, &observerList);
240     if (observerList)
241     {
242         BDBLOG(("  got observerList\n"));
243         DBusService *service = DBusService::GetSingleton();
244         
245         for (PRInt32 i = 0; i < observerList->Length(); ++i) {
246             nsCAutoString t;
247             nsCOMPtr<IDBusSignal> signal = do_QueryReferent((*observerList)[i]);
248             signal->GetInterfaceName(t);
249             BDBLOG(("    interface : %s\n", PromiseFlatCString(t).get()));
250             signal->GetSignalName(t);
251             BDBLOG(("    signal    : %s\n", PromiseFlatCString(t).get()));
252             signal->GetSender(t);
253             BDBLOG(("    sender    : %s\n", PromiseFlatCString(t).get()));
254             if (!t.IsEmpty() && !t.Equals(dbus_message_get_sender(message)))
255             {
256                 BDBLOG(("    sender does not match\n"));
257                 break;
258             }
259             signal->GetObjectPath(t);
260             BDBLOG(("    object    : %s\n", PromiseFlatCString(t).get()));
261             if (!t.IsEmpty() && !t.Equals(dbus_message_get_path(message)))
262             {
263                 BDBLOG(("    objectPath does not match\n"));
264                 break;
265             }
266
267             /* do callback */
268
269             DBusMessageIter iter;
270             nsCOMPtr<nsIMutableArray> args_array;
271
272             dbus_message_iter_init(message, &iter);
273             args_array = getArrayFromIter(&iter);
274
275             PRUint32 arg_items;
276             args_array->GetLength(&arg_items);
277             BDBLOG(("  arg_items: %d items\n", arg_items));
278
279             /* arguments are packed as an array into an nsIVariant */
280             nsIVariant **callback_args = new nsIVariant*[arg_items];
281             nsCOMPtr<nsIWritableVariant> args = do_CreateInstance("@mozilla.org/variant;1");
282             for (int i = 0; i < arg_items; i++)
283             {
284                 nsCOMPtr<nsIVariant> arg = do_QueryElementAt(args_array, i);
285                 callback_args[i] = arg;
286                 NS_ADDREF(arg);
287             }
288             args->SetAsArray(nsIDataType::VTYPE_INTERFACE_IS,
289                              &NS_GET_IID(nsIVariant),
290                              arg_items,
291                              callback_args);
292             for (int i = 0; i < arg_items; i++)
293                 NS_RELEASE(callback_args[i]);
294             delete[] callback_args;
295
296             nsCOMPtr<IDBusSignalObserver> callback;
297             signal->GetOnEmit(getter_AddRefs(callback));
298
299             service->SetInsideEmit(TRUE);
300             callback->OnSignal(args);
301             service->SetInsideEmit(FALSE);
302
303         }
304         
305         /* Check if we have queued observer changes */
306         service->CheckSignalObserverQueue();
307         
308         return DBUS_HANDLER_RESULT_HANDLED;
309     }
310     else
311     {
312         BDBLOG(("  no observer found\n"));
313         return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
314     }
315 }
316
317 static
318 void BuildRule(IDBusSignal *aSignal, nsACString& aRetval)
319 {
320     nsCAutoString tmp;
321
322     aRetval.Assign(NS_LITERAL_CSTRING("type='signal',interface='"));
323     aSignal->GetInterfaceName(tmp);
324     aRetval.Append(tmp);
325     aRetval.Append(NS_LITERAL_CSTRING("',member='"));
326     aSignal->GetSignalName(tmp);
327     aRetval.Append(tmp);
328     aRetval.Append(NS_LITERAL_CSTRING("'"));
329 }
330
331 void DBusService::CheckSignalObserverQueue()
332 {
333     BDBLOG((__FUNCTION__));
334
335     PRUint32 i = mRemovedSignals.Length();
336     while (i-- > 0)
337     {
338         RemoveSignalObserver(mRemovedSignals[i]);
339         mRemovedSignals.RemoveElementAt(i);
340     }
341     i = mAddedSignals.Length();
342     while (i-- > 0)
343     {
344         AddSignalObserver(mAddedSignals[i]);
345         mAddedSignals.RemoveElementAt(i);
346     }
347 }
348
349 void DBusService::AddSignalObserver(IDBusSignal *aSignal)
350 {
351     nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *signalObservers = nsnull;
352     nsCAutoString observerKey;
353     nsCAutoString tmp;
354
355     if (mInsideEmit)
356     {
357         mAddedSignals.AppendElement(aSignal);
358         return;
359     }
360
361     BDBLOG(("DBusService::AddSignalObserver()\n"));
362
363     aSignal->GetInterfaceName(tmp);
364     BDBLOG(("  aInterface : %s\n", PromiseFlatCString(tmp).get()));
365     observerKey.Assign(tmp);
366     observerKey.Append(".");
367
368     aSignal->GetSignalName(tmp);
369     BDBLOG(("  aSignal    : %s\n", PromiseFlatCString(tmp).get()));
370     observerKey.Append(tmp);
371
372     BDBLOG(("  observerKey: %s\n", PromiseFlatCString(observerKey).get()));
373
374     nsTArray<nsWeakPtr> *observerList = nsnull;
375
376     PRUint32 bus_type = 0;
377     aSignal->GetBusType(&bus_type);
378     if (bus_type == SYSTEM)
379         signalObservers = &mSystemBusSignalObservers;
380     else if (bus_type == SESSION)
381         signalObservers = &mSessionBusSignalObservers;
382     signalObservers->Get(observerKey, &observerList);
383     if (observerList)
384     {
385         /* append to list */
386         BDBLOG(("  got observerList\n"));
387         nsCOMPtr<nsISupportsWeakReference> weakRefable = do_QueryInterface(aSignal);
388         nsWeakPtr weakPtr = getter_AddRefs(NS_GetWeakReference(weakRefable));
389         observerList->AppendElement(weakPtr);
390     }
391     else
392     {
393         /* create a new list */
394         BDBLOG(("  no observerList found\n"));
395         observerList = new nsTArray<nsWeakPtr>;
396         nsCOMPtr<nsISupportsWeakReference> weakRefable = do_QueryInterface(aSignal);
397         nsWeakPtr weakPtr = getter_AddRefs(NS_GetWeakReference(weakRefable));
398         observerList->AppendElement(weakPtr);
399         signalObservers->Put(observerKey, observerList);
400         
401         /* add match rule for interface.signal */
402         PRUint32 busType;
403         nsCAutoString matchRule;
404
405         aSignal->GetBusType(&busType);
406         BuildRule(aSignal, matchRule);
407         BDBLOG(("  new match rule: %s\n", PromiseFlatCString(matchRule).get()));
408         dbus_bus_add_match(GetConnection(busType),
409                            PromiseFlatCString(matchRule).get(),
410                            nsnull);
411     }
412 }
413
414 void DBusService::RemoveSignalObserver(IDBusSignal *aSignal)
415 {
416     nsClassHashtable<nsCStringHashKey, nsTArray<nsWeakPtr> > *signalObservers = nsnull;
417     nsCAutoString observerKey;
418     nsCAutoString tmp;
419
420     if (mInsideEmit)
421     {
422         mRemovedSignals.AppendElement(aSignal);
423         return;
424     }
425
426     BDBLOG(("DBusService::RemoveSignalObserver()\n"));
427
428     aSignal->GetInterfaceName(tmp);
429     BDBLOG(("  aInterface : %s\n", PromiseFlatCString(tmp).get()));
430     observerKey.Assign(tmp);
431     observerKey.Append(".");
432
433     aSignal->GetSignalName(tmp);
434     BDBLOG(("  aSignal    : %s\n", PromiseFlatCString(tmp).get()));
435     observerKey.Append(tmp);
436
437     BDBLOG(("  observerKey: %s\n", PromiseFlatCString(observerKey).get()));
438
439     nsTArray<nsWeakPtr> *observerList = nsnull;
440
441     PRUint32 bus_type = 0;
442     aSignal->GetBusType(&bus_type);
443     if (bus_type == SYSTEM)
444         signalObservers = &mSystemBusSignalObservers;
445     else if (bus_type == SESSION)
446         signalObservers = &mSessionBusSignalObservers;
447     signalObservers->Get(observerKey, &observerList);
448     if (observerList)
449     {
450         BDBLOG(("  got observerList\n"));
451         nsCOMPtr<nsISupportsWeakReference> weakRefable = do_QueryInterface(aSignal);
452         nsWeakPtr weakPtr = getter_AddRefs(NS_GetWeakReference(weakRefable));
453         for (PRInt32 i = 0; i < observerList->Length(); ++i) {
454             nsCAutoString t;
455             nsCAutoString ob;
456             nsCOMPtr<IDBusSignal> signal = do_QueryReferent((*observerList)[i]);
457             signal->GetInterfaceName(t);
458             ob.Assign(t);
459             ob.Append(".");
460             signal->GetSignalName(t);
461             ob.Append(t);
462             BDBLOG(("    signal : %s\n", PromiseFlatCString(ob).get()));
463         }
464         BDBLOG(("  call observerList->RemoveElement\n"));
465         observerList->RemoveElement(weakPtr);
466         for (PRInt32 i = 0; i < observerList->Length(); ++i) {
467             nsCAutoString t;
468             nsCAutoString ob;
469             nsCOMPtr<IDBusSignal> signal = do_QueryReferent((*observerList)[i]);
470             signal->GetInterfaceName(t);
471             ob.Assign(t);
472             ob.Append(".");
473             signal->GetSignalName(t);
474             ob.Append(t);
475             BDBLOG(("    signal : %s\n", PromiseFlatCString(ob).get()));
476         }
477
478         // if list is empty, remove match rule
479         if (observerList->Length() == 0)
480         {
481             PRUint32 busType;
482             nsCAutoString matchRule;
483
484             aSignal->GetBusType(&busType);
485             BuildRule(aSignal, matchRule);
486             BDBLOG(("  remove match rule: %s\n", PromiseFlatCString(matchRule).get()));
487             dbus_bus_remove_match(GetConnection(busType),
488                                   PromiseFlatCString(matchRule).get(),
489                                   nsnull);
490             signalObservers->Remove(observerKey); 
491         }
492         BDBLOG(("  done\n"));
493     }
494     else
495     {
496         BDBLOG(("  ERROR: no observerList found!\n"));
497     }
498 }
499
500 DBusConnection *DBusService::GetConnection(PRUint32 aConnType)
501 {
502     BDBLOG(("DBusService::GetConnection(%d)\n", aConnType));
503
504     if (aConnType == SYSTEM)
505     {
506         if (mSystemBus == nsnull)
507         {
508             mSystemBus = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
509             if (!mSystemBus)
510                 return nsnull;
511             dbus_connection_set_exit_on_disconnect(mSystemBus, PR_FALSE);
512             dbus_connection_setup_with_g_main(mSystemBus, NULL);
513         }
514         return mSystemBus;
515     }
516     else if (aConnType == SESSION)
517     {
518         if (mSessionBus == nsnull)
519         {
520             mSessionBus = dbus_bus_get(DBUS_BUS_SESSION, NULL);
521             if (!mSessionBus)
522                 return nsnull;
523             dbus_connection_set_exit_on_disconnect(mSessionBus, PR_FALSE);
524             dbus_connection_setup_with_g_main(mSessionBus, NULL);
525         }
526         return mSessionBus;
527     }
528     return nsnull;
529 }
530
531 DBusService *
532 DBusService::GetSingleton()
533 {
534     BDBLOG(("DBusService::GetSingleton() called: "));
535
536     if (!gDBusService)
537     {
538         BDBLOG(("creating new DBusService\n"));
539         gDBusService = new DBusService();
540     }
541
542     if (gDBusService)
543     {
544         BDBLOG(("adding reference to existing DBusService\n"));
545         NS_ADDREF(gDBusService);
546     }
547
548     return gDBusService;
549 }
550
551
552
553 //
554 // Module implementation
555 //
556
557 NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(DBusService, DBusService::GetSingleton);
558
559 static const nsModuleComponentInfo components[] =
560 {
561     {
562         "DBus service",
563         DBUSSERVICE_CID,
564         "@movial.fi/dbus/service;1",
565         DBusServiceConstructor
566     },
567     {
568         "DBus method",
569         DBUSMETHOD_CID,
570         "@movial.fi/dbus/method;1",
571         nsnull
572     },
573     {
574         "DBus signal",
575         DBUSSIGNAL_CID,
576         "@movial.fi/dbus/signal;1",
577         nsnull
578     }
579 };
580
581 NS_IMPL_NSGETMODULE(nsDBusServiceModule, components);
582
583
584
585
586 /* vim: set cindent ts=4 et sw=4: */