[XPCOM] Keep the JS context with the Service/Method objects, we
[browser-dbus-bridge.git] / xpcom-dbusservice / DBusMethod.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 <dbus/dbus.h>
29
30 #include "nsComponentManagerUtils.h"
31 #include "nsIMutableArray.h"
32 #include "nsArrayUtils.h"
33 #include "nsISupportsPrimitives.h"
34 #include "nsIProperties.h"
35 #include "nsIXPConnect.h"
36
37 #include "IDBusService.h"
38 #include "DBusMethod.h"
39 #include "DBusMarshaling.h"
40
41 #include "bdb-debug.h"
42
43 //
44 // helper declarations
45 //
46
47 static void DoCallBack(DBusMethod *aCallback, DBusMessage *aReply);
48 static void ReplyHandler(DBusPendingCall *pending, void *user_data);
49
50 //
51 // DBusMethod implementation
52 //
53
54 NS_IMPL_ISUPPORTS1(DBusMethod, IDBusMethod)
55
56 DBusMethod::DBusMethod(DBusService *aDBusService,
57                        PRUint32 aBusType,
58                        const nsACString& aDestination,
59                        const nsACString& aObjectPath,
60                        const nsACString& aMethodName,
61                        const nsACString& aInterfaceName,
62                        const nsACString& aSignature,
63                        JSContext *cx) :
64     mDBusService(aDBusService),
65     mBusType(aBusType),
66     mDestination(aDestination),
67     mObject(aObjectPath),
68     mMethod(aMethodName),
69     mInterface(aInterfaceName),
70     mSignature(aSignature),
71     mAsync(PR_TRUE),
72     mCallback(nsnull),
73     mErrorCallback(nsnull),
74     mJScx(cx)
75 {
76     BDBLOG(("DBusMethod::DBusMethod()\n"));
77     BDBLOG(("  aBusType          : %d\n", aBusType));
78     BDBLOG(("  aDestination      : %s\n", PromiseFlatCString(aDestination).get()));
79     BDBLOG(("  aObjectPath       : %s\n", PromiseFlatCString(aObjectPath).get()));
80     BDBLOG(("  aMethodName       : %s\n", PromiseFlatCString(aMethodName).get()));
81     BDBLOG(("  aInterfaceName    : %s\n", PromiseFlatCString(aInterfaceName).get()));
82     BDBLOG(("  aSignature        : %s\n", PromiseFlatCString(aSignature).get()));
83
84 }
85
86 DBusMethod::~DBusMethod()
87 {
88     BDBLOG(("DBusMethod::~DBusMethod()\n"));
89     if (mCallback)
90         NS_RELEASE(mCallback);
91     if (mErrorCallback)
92         NS_RELEASE(mErrorCallback);
93 }
94
95 NS_IMETHODIMP
96 DBusMethod::GetAsync(PRBool *aAsync)
97 {
98     *aAsync = mAsync;
99     return NS_OK;
100 }
101
102 NS_IMETHODIMP
103 DBusMethod::SetAsync(PRBool aAsync)
104 {
105     BDBLOG(("DBusMethod::SetAsync(%s)\n", aAsync ? "true" : "false"));
106     mAsync = aAsync;
107     return NS_OK;
108 }
109
110 NS_IMETHODIMP
111 DBusMethod::GetOnReply(IDBusMethodCallback **aOnReply)
112 {
113     *aOnReply = mCallback;
114     NS_IF_ADDREF(*aOnReply);
115     return NS_OK;
116 }
117
118 NS_IMETHODIMP
119 DBusMethod::SetOnReply(IDBusMethodCallback *aOnReply)
120 {
121     BDBLOG(("DBusMethod::SetOnReply(%08x)\n", aOnReply));
122     if (mCallback)
123         NS_RELEASE(mCallback);
124     mCallback = aOnReply;
125     NS_IF_ADDREF(mCallback);
126     return NS_OK;
127 }
128
129 NS_IMETHODIMP
130 DBusMethod::GetOnError(IDBusMethodCallback **aOnError)
131 {
132     *aOnError = mErrorCallback;
133     NS_IF_ADDREF(*aOnError);
134     return NS_OK;
135 }
136
137 NS_IMETHODIMP
138 DBusMethod::SetOnError(IDBusMethodCallback *aOnError)
139 {
140     BDBLOG(("DBusMethod::SetOnError(%08x)\n", aOnError));
141     if (mErrorCallback)
142         NS_RELEASE(mErrorCallback);
143     mErrorCallback = aOnError;
144     NS_IF_ADDREF(mErrorCallback);
145     return NS_OK;
146 }
147
148 NS_IMETHODIMP
149 DBusMethod::GetJSContext(JSContext **aJSContext)
150 {
151     *aJSContext = mJScx;
152     return NS_OK;
153 }
154
155 static void
156 ReplyHandler(DBusPendingCall *pending, void *user_data)
157 {
158     DBusMessage *reply;
159     nsCOMPtr<DBusMethod> method = (DBusMethod *) user_data;
160
161     reply = dbus_pending_call_steal_reply(pending);
162     DoCallBack(method, reply);
163     dbus_message_unref(reply);
164 }
165
166
167 static void
168 DoCallBack(DBusMethod *aMethod, DBusMessage *aReply)
169 {
170     DBusMessageIter iter;
171     int current_type;
172     nsCOMPtr<nsIMutableArray> reply_args;
173     nsCOMPtr<IDBusMethodCallback> callback;
174
175     int msg_type = dbus_message_get_type(aReply);
176
177     dbus_message_iter_init(aReply, &iter);
178
179     JSContext *cx;
180     aMethod->GetJSContext(&cx);
181     reply_args = getArrayFromIter(cx, &iter);
182
183     switch (msg_type)
184     {
185         case DBUS_MESSAGE_TYPE_METHOD_RETURN:
186         {
187             BDBLOG(("  got method reply\n"));
188             aMethod->GetOnReply(getter_AddRefs(callback));
189             break;
190         }
191         case DBUS_MESSAGE_TYPE_ERROR:
192         {
193             BDBLOG(("  got an error message: %s\n", dbus_message_get_error_name(aReply)));
194             aMethod->GetOnError(getter_AddRefs(callback));
195
196             /* insert error name as first callback argument */
197             nsCOMPtr<nsIWritableVariant> error_name = do_CreateInstance("@mozilla.org/variant;1");
198             error_name->SetAsString(dbus_message_get_error_name(aReply));
199             reply_args->InsertElementAt(error_name, 0, PR_FALSE);
200
201             break;
202         }
203         default:
204         {
205             BDBLOG(("  got unhandled message of type %d\n", msg_type));
206             break;
207         }
208     }
209
210     PRUint32 reply_items;
211     reply_args->GetLength(&reply_items);
212     BDBLOG(("  reply_args: %d items\n", reply_items));
213
214     if (callback)
215     {
216         /* arguments are packed as an array into an nsIVariant */
217         nsIVariant **callback_args = new nsIVariant*[reply_items];
218         nsCOMPtr<nsIWritableVariant> args = do_CreateInstance("@mozilla.org/variant;1");
219         for (PRUint32 i = 0; i < reply_items; i++)
220         {
221             nsCOMPtr<nsIVariant> arg = do_QueryElementAt(reply_args, i);
222             callback_args[i] = arg;
223             NS_ADDREF(arg);
224         }
225         args->SetAsArray(nsIDataType::VTYPE_INTERFACE_IS,
226                          &NS_GET_IID(nsIVariant),
227                          reply_items,
228                          callback_args);
229         for (PRUint32 i = 0; i < reply_items; i++)
230             NS_RELEASE(callback_args[i]);
231         delete[] callback_args;
232         callback->OnReply(args);
233     }
234 }
235
236 NS_IMETHODIMP
237 DBusMethod::DoCall(nsIVariant **aArgs, PRUint32 aCount)
238 {
239     DBusMessage *msg;
240     DBusMessageIter msg_iter;
241     nsCAutoString signature;
242
243     BDBLOG(("DBusMethod::DoCall()\n"));
244     BDBLOG(("  aCount          : %d\n", aCount));
245
246     msg = dbus_message_new_method_call(PromiseFlatCString(mDestination).get(),
247                                        PromiseFlatCString(mObject).get(),
248                                        PromiseFlatCString(mInterface).get(),
249                                        PromiseFlatCString(mMethod).get());
250     dbus_message_iter_init_append(msg, &msg_iter);
251
252     if (mSignature.Equals(""))
253     {
254         // FIXME - is it necessary to clear the string?
255         signature.Assign("");
256         for (int i = 0; i < aCount; i++)
257         {
258             // no method signature specified, guess argument types
259             nsCOMPtr<nsIVariant> data = aArgs[i];
260             nsCAutoString tmpsig;
261
262             getSignatureFromVariant(mJScx, data, tmpsig);
263             BDBLOG(("  aArgs[%02d]       : signature \"%s\"\n",
264                    i,
265                    PromiseFlatCString(tmpsig).get()));
266             signature.Append(tmpsig);
267
268         } /* for (int i = 0; i < aCount; i++) */
269     } /* if (mSignature.Equals("")) */
270     else
271     {
272         signature.Assign(mSignature);
273     }
274
275     if (dbus_signature_validate(PromiseFlatCString(signature).get(), nsnull))
276     {
277         DBusSignatureIter sig_iter;
278         int current_type;
279         int i = 0;
280
281         BDBLOG(("  signature \"%s\"\n", PromiseFlatCString(signature).get()));
282
283         dbus_signature_iter_init(&sig_iter, PromiseFlatCString(signature).get());
284         while ((current_type = dbus_signature_iter_get_current_type(&sig_iter)) != DBUS_TYPE_INVALID)
285         {
286             char *element_signature = dbus_signature_iter_get_signature(&sig_iter);
287             BDBLOG(("  element \"%s\" from signature\n", element_signature));
288             BDBLOG(("  type %c from signature\n", current_type));
289
290             addVariantToIter(mJScx, aArgs[i], &msg_iter, &sig_iter);
291
292             i++;
293             dbus_free(element_signature);
294             dbus_signature_iter_next(&sig_iter);
295         }
296     }
297     else
298     {
299         BDBLOG(("  invalid signature \"%s\"\n", PromiseFlatCString(signature).get()));
300         return NS_ERROR_ILLEGAL_VALUE;
301     }
302
303     // Sanity-check: make sure that the signature we think we are sending matches
304     // that of the message
305     
306     if (!signature.Equals(dbus_message_get_signature(msg)))
307     {
308         BDBLOG(("  signature mismatch! Expected '%s', got '%s'\n",
309                 PromiseFlatCString(signature).get(),
310                 dbus_message_get_signature(msg)));
311         return NS_ERROR_ILLEGAL_VALUE;
312     }
313
314     DBusPendingCall *pending = mDBusService->SendWithReply(mBusType,
315                                                            msg,
316                                                            -1);
317     if (pending)
318     {
319         if (mAsync)
320         {
321             /* FIXME - do we need to AddRef "this" */
322             BDBLOG(("  do async reply callback\n"));
323             dbus_pending_call_set_notify(pending,
324                                          ReplyHandler,
325                                          this,
326                                          nsnull);
327         }
328         else
329         {
330             DBusMessage *reply;
331             BDBLOG(("  do sync reply callback\n"));
332             dbus_pending_call_block(pending);
333             reply = dbus_pending_call_steal_reply (pending);
334             dbus_pending_call_unref (pending);
335             DoCallBack(this, reply);
336             dbus_message_unref(reply);
337         }
338     }
339     else
340     {
341         return NS_ERROR_OUT_OF_MEMORY;
342     }
343
344     dbus_message_unref(msg);
345     return NS_OK;
346 }
347
348
349 /* vim: set cindent ts=4 et sw=4: */