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