[xpcom] Make sure the method survives the duration of an async call
[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.com>
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     DBusMethod *method = (DBusMethod *) user_data;
160
161     reply = dbus_pending_call_steal_reply(pending);
162     DoCallBack(method, reply);
163     dbus_message_unref(reply);
164     // We took a ref when starting the async call, release it here
165     method->Release();
166 }
167
168
169 static void
170 DoCallBack(DBusMethod *aMethod, DBusMessage *aReply)
171 {
172     DBusMessageIter iter;
173     nsCOMPtr<nsIMutableArray> reply_args;
174     nsCOMPtr<IDBusMethodCallback> callback;
175
176     int msg_type = dbus_message_get_type(aReply);
177
178     dbus_message_iter_init(aReply, &iter);
179
180     JSContext *cx;
181     aMethod->GetJSContext(&cx);
182     reply_args = getArrayFromIter(cx, &iter);
183
184     switch (msg_type)
185     {
186         case DBUS_MESSAGE_TYPE_METHOD_RETURN:
187         {
188             BDBLOG(("  got method reply\n"));
189             aMethod->GetOnReply(getter_AddRefs(callback));
190             break;
191         }
192         case DBUS_MESSAGE_TYPE_ERROR:
193         {
194             BDBLOG(("  got an error message: %s\n", dbus_message_get_error_name(aReply)));
195             aMethod->GetOnError(getter_AddRefs(callback));
196
197             /* insert error name as first callback argument */
198             nsCOMPtr<nsIWritableVariant> error_name = do_CreateInstance("@mozilla.org/variant;1");
199             error_name->SetAsString(dbus_message_get_error_name(aReply));
200             reply_args->InsertElementAt(error_name, 0, PR_FALSE);
201
202             break;
203         }
204         default:
205         {
206             BDBLOG(("  got unhandled message of type %d\n", msg_type));
207             break;
208         }
209     }
210
211     PRUint32 reply_items;
212     reply_args->GetLength(&reply_items);
213     BDBLOG(("  reply_args: %d items\n", reply_items));
214
215     if (callback)
216     {
217         /* arguments are packed as an array into an nsIVariant */
218         nsIVariant **callback_args = new nsIVariant*[reply_items];
219         nsCOMPtr<nsIWritableVariant> args = do_CreateInstance("@mozilla.org/variant;1");
220         for (PRUint32 i = 0; i < reply_items; i++)
221         {
222             nsCOMPtr<nsIVariant> arg = do_QueryElementAt(reply_args, i);
223             callback_args[i] = arg;
224             NS_ADDREF(arg);
225         }
226         args->SetAsArray(nsIDataType::VTYPE_INTERFACE_IS,
227                          &NS_GET_IID(nsIVariant),
228                          reply_items,
229                          callback_args);
230         for (PRUint32 i = 0; i < reply_items; i++)
231             NS_RELEASE(callback_args[i]);
232         delete[] callback_args;
233         callback->OnReply(args);
234     }
235 }
236
237 NS_IMETHODIMP
238 DBusMethod::DoCall(nsIVariant **aArgs, PRUint32 aCount)
239 {
240     DBusMessage *msg;
241     DBusMessageIter msg_iter;
242     nsCAutoString signature;
243
244     BDBLOG(("DBusMethod::DoCall()\n"));
245     BDBLOG(("  aCount          : %d\n", aCount));
246
247     msg = dbus_message_new_method_call(PromiseFlatCString(mDestination).get(),
248                                        PromiseFlatCString(mObject).get(),
249                                        PromiseFlatCString(mInterface).get(),
250                                        PromiseFlatCString(mMethod).get());
251     dbus_message_iter_init_append(msg, &msg_iter);
252
253     if (mSignature.Equals(""))
254     {
255         // FIXME - is it necessary to clear the string?
256         signature.Assign("");
257         for (PRUint32 i = 0; i < aCount; i++)
258         {
259             // no method signature specified, guess argument types
260             nsCOMPtr<nsIVariant> data = aArgs[i];
261             nsCAutoString tmpsig;
262
263             getSignatureFromVariant(mJScx, data, tmpsig);
264             BDBLOG(("  aArgs[%02d]       : signature \"%s\"\n",
265                    i,
266                    PromiseFlatCString(tmpsig).get()));
267             signature.Append(tmpsig);
268
269         } /* for (int i = 0; i < aCount; i++) */
270     } /* if (mSignature.Equals("")) */
271     else
272     {
273         signature.Assign(mSignature);
274     }
275
276     if (dbus_signature_validate(PromiseFlatCString(signature).get(), nsnull))
277     {
278         DBusSignatureIter sig_iter;
279         int current_type;
280         int i = 0;
281
282         BDBLOG(("  signature \"%s\"\n", PromiseFlatCString(signature).get()));
283
284         dbus_signature_iter_init(&sig_iter, PromiseFlatCString(signature).get());
285         while ((current_type = dbus_signature_iter_get_current_type(&sig_iter)) != DBUS_TYPE_INVALID)
286         {
287             char *element_signature = dbus_signature_iter_get_signature(&sig_iter);
288             BDBLOG(("  element \"%s\" from signature\n", element_signature));
289             BDBLOG(("  type %c from signature\n", current_type));
290
291             addVariantToIter(mJScx, aArgs[i], &msg_iter, &sig_iter);
292
293             i++;
294             dbus_free(element_signature);
295             dbus_signature_iter_next(&sig_iter);
296         }
297     }
298     else
299     {
300         BDBLOG(("  invalid signature \"%s\"\n", PromiseFlatCString(signature).get()));
301         return NS_ERROR_ILLEGAL_VALUE;
302     }
303
304     // Sanity-check: make sure that the signature we think we are sending matches
305     // that of the message
306     
307     if (!signature.Equals(dbus_message_get_signature(msg)))
308     {
309         BDBLOG(("  signature mismatch! Expected '%s', got '%s'\n",
310                 PromiseFlatCString(signature).get(),
311                 dbus_message_get_signature(msg)));
312         return NS_ERROR_ILLEGAL_VALUE;
313     }
314
315     DBusPendingCall *pending = mDBusService->SendWithReply(mBusType,
316                                                            msg,
317                                                            -1);
318     if (pending)
319     {
320         if (mAsync)
321         {
322             BDBLOG(("  do async reply callback\n"));
323             if (dbus_pending_call_set_notify(pending,
324                                              ReplyHandler,
325                                              this,
326                                              nsnull))
327             {
328                 // Add a ref to make sure the method object doesn't die
329                 // before the reply comes.
330                 this->AddRef();
331             }
332         }
333         else
334         {
335             DBusMessage *reply;
336             BDBLOG(("  do sync reply callback\n"));
337             dbus_pending_call_block(pending);
338             reply = dbus_pending_call_steal_reply (pending);
339             DoCallBack(this, reply);
340             dbus_message_unref(reply);
341         }
342         dbus_pending_call_unref (pending);
343     }
344     else
345     {
346         return NS_ERROR_OUT_OF_MEMORY;
347     }
348
349     dbus_message_unref(msg);
350     return NS_OK;
351 }
352
353
354 /* vim: set cindent ts=4 et sw=4: */