1 /*
2 * Copyright (C) 2007-2008 Collabora Ltd. <http://www.collabora.co.uk/>
3 * Copyright (C) 2007-2008 Nokia Corporation
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2.1 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 */
19
20 #include "config.h"
21
22 #include "telepathy-glib/proxy-subclass.h"
23 #include "telepathy-glib/proxy-internal.h"
24
25 #define DEBUG_FLAG TP_DEBUG_PROXY
26 #include "telepathy-glib/debug-internal.h"
27 #include <telepathy-glib/util.h>
28
29 #if 0
30 #define MORE_DEBUG DEBUG
31 #else
32 #define MORE_DEBUG(...) G_STMT_START {} G_STMT_END
33 #endif
34
35 /**
36 * TpProxyPendingCall:
37 *
38 * Opaque structure representing a pending D-Bus call.
39 *
40 * Since: 0.7.1
41 */
42
43 struct _TpProxyPendingCall {
44 /* This structure's "reference count" is implicit:
45 * - 1 if D-Bus has us (from creation until _completed)
46 * - 1 if results have come in but we haven't run the callback yet
47 * (idle_source is nonzero)
48 *
49 * In normal use, its life cycle should go like this:
50 * - Created by tp_proxy_pending_call_v0_new
51 * - Given to dbus-glib by generated code (actual call starts here)
52 * - tp_proxy_pending_call_v0_take_pending_call
53 * - (Phase 1)
54 * - tp_proxy_pending_call_v0_take_results
55 * - Idle handler queued
56 * - (Phase 2)
57 * - tp_proxy_pending_call_v0_completed
58 * - (Phase 3)
59 * - tp_proxy_pending_call_idle_invoke
60 * - tp_proxy_pending_call_free
61 *
62 * although we can't guarantee that idle_invoke won't go off before
63 * completed does, if the dbus-glib implementation changes.
64 *
65 * Exceptional conditions that can occur:
66 * - Weak object dies
67 * - Reference cleared, otherwise equivalent to explicit cancellation
68 * - Explicitly cancelled
69 * - All phases: callback invoked if cancel_must_raise, otherwise
70 * not
71 * - DBusGProxy destroy signal (or _completed before _take_results)
72 * - Phase 1: error callback queued
73 * - Phase 2: ignored, we use the results we've already got
74 * - Phase 3: ignored, we use the results we've already got
75 */
76
77 /* Always non-NULL */
78 TpProxy *proxy;
79
80 /* Set to NULL after it's been invoked once, or if cancellation means
81 * it should never be called. Supplied by the generated code */
82 TpProxyInvokeFunc invoke_callback;
83
84 /* arguments for invoke_callback supplied by _take_results, by
85 * cancellation or by the destroy signal */
86 GError *error /* implicitly initialized */;
87 GValueArray *args;
88
89 /* user-supplied arguments for invoke_callback */
90 GCallback callback;
91 gpointer user_data;
92 GDestroyNotify destroy;
93 GObject *weak_object;
94
95 /* Non-NULL until either _completed or destroy, whichever comes first */
96 DBusGProxy *iface_proxy;
97 DBusGProxyCall *pending_call;
98
99 /* Nonzero if _idle_invoke has been queued (even if it has already
100 * happened), i.e. if results have been taken or the DBusGProxy
101 * was destroyed */
102 guint idle_source;
103
104 /* If TRUE, invoke the callback even on cancellation */
105 unsigned cancel_must_raise:1;
106
107 /* If TRUE, the idle_invoke callback has either run or been cancelled */
108 unsigned idle_completed:1;
109 /* If TRUE, dbus-glib no longer holds a reference to us */
110 unsigned dbus_completed:1;
111
112 /* Marker to indicate that this is, in fact, a valid TpProxyPendingCall */
113 gconstpointer priv;
114 };
115
116 static const gchar * const pending_call_magic = "TpProxyPendingCall";
117
118 static void
tp_proxy_pending_call_lost_weak_ref(gpointer data,GObject * dead)119 tp_proxy_pending_call_lost_weak_ref (gpointer data,
120 GObject *dead)
121 {
122 TpProxyPendingCall *pc = data;
123
124 DEBUG ("%p lost weak ref to %p", pc, dead);
125
126 g_assert (pc->priv == pending_call_magic);
127 g_assert (dead == pc->weak_object);
128
129 pc->weak_object = NULL;
130
131 if (!pc->idle_completed)
132 tp_proxy_pending_call_cancel (pc);
133 }
134
135 static gboolean
tp_proxy_pending_call_idle_invoke(gpointer p)136 tp_proxy_pending_call_idle_invoke (gpointer p)
137 {
138 TpProxyPendingCall *pc = p;
139 TpProxyInvokeFunc invoke = pc->invoke_callback;
140
141 MORE_DEBUG ("%p", pc);
142
143 if (invoke == NULL)
144 {
145 /* either already invoked (bug?), or cancelled */
146 return FALSE;
147 }
148
149 MORE_DEBUG ("%p: invoking user callback", pc);
150
151 g_assert (pc->proxy != NULL);
152 g_assert (pc->error == NULL || pc->args == NULL);
153 g_assert (!pc->idle_completed);
154
155 pc->invoke_callback = NULL;
156 invoke (pc->proxy, pc->error, pc->args, pc->callback,
157 pc->user_data, pc->weak_object);
158 pc->error = NULL;
159 pc->args = NULL;
160
161 /* don't clear pc->idle_source here! tp_proxy_pending_call_v0_completed
162 * compares it to 0 to determine whether to free the object */
163
164 return FALSE;
165 }
166
167 static void _tp_proxy_pending_call_idle_completed (gpointer p);
168
169 static void
_tp_proxy_pending_call_dgproxy_destroy(DBusGProxy * iface_proxy,TpProxyPendingCall * pc)170 _tp_proxy_pending_call_dgproxy_destroy (DBusGProxy *iface_proxy,
171 TpProxyPendingCall *pc)
172 {
173 g_assert (iface_proxy != NULL);
174 g_assert (pc != NULL);
175 g_assert (pc->iface_proxy == iface_proxy);
176 g_assert (pc->proxy != NULL);
177
178 DEBUG ("%p: DBusGProxy %p invalidated", pc, iface_proxy);
179
180 if (pc->idle_source == 0)
181 {
182 /* we haven't already received and queued a reply, so synthesize
183 * one */
184 g_assert (pc->args == NULL);
185 g_assert (pc->error == NULL);
186
187 pc->error = g_error_new_literal (TP_DBUS_ERRORS,
188 TP_DBUS_ERROR_NAME_OWNER_LOST, "Name owner lost (service crashed?)");
189
190 pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
191 tp_proxy_pending_call_idle_invoke, pc,
192 _tp_proxy_pending_call_idle_completed);
193 }
194
195 g_signal_handlers_disconnect_by_func (pc->iface_proxy,
196 _tp_proxy_pending_call_dgproxy_destroy, pc);
197 g_object_unref (pc->iface_proxy);
198 pc->iface_proxy = NULL;
199 }
200
201 /**
202 * tp_proxy_pending_call_v0_new:
203 * @self: a proxy
204 * @iface: a quark whose string value is the D-Bus interface
205 * @member: the name of the method being called
206 * @iface_proxy: the interface-specific #DBusGProxy for @iface
207 * @invoke_callback: an implementation of #TpProxyInvokeFunc which will
208 * invoke @callback with appropriate arguments
209 * @callback: a callback to be called when the call completes
210 * @user_data: user-supplied data for the callback
211 * @destroy: user-supplied destructor for the data
212 * @weak_object: if not %NULL, a #GObject which will be weakly referenced by
213 * the signal connection - if it is destroyed, the pending call will
214 * automatically be cancelled
215 * @cancel_must_raise: if %TRUE, the @invoke_callback will be run with
216 * error %TP_DBUS_ERROR_CANCELLED if the call is cancelled by a call to
217 * tp_proxy_pending_call_cancel() or by destruction of the @weak_object;
218 * if %FALSE, the @invoke_callback will not be run at all in these cases
219 *
220 * Allocate a new pending call structure. After calling this function, the
221 * caller must start an asynchronous D-Bus call and give the resulting
222 * DBusGProxyCall to the pending call object using
223 * tp_proxy_pending_call_v0_take_pending_call().
224 *
225 * If dbus-glib gets a reply to the call before it's cancelled, the caller
226 * must arrange for tp_proxy_pending_call_v0_take_results() to be called
227 * with the results (the intention is for this to be done immediately
228 * after dbus_g_proxy_end_call in the callback supplied to dbus-glib).
229 *
230 * When dbus-glib discards its reference to the user_data supplied in the
231 * asynchronous D-Bus call (i.e. after the call is cancelled or a reply
232 * arrives), tp_proxy_pending_call_v0_completed must be called (the intention
233 * is for the #TpProxyPendingCall to be the @user_data in the async call,
234 * and for tp_proxy_pending_call_v0_completed to be the #GDestroyNotify
235 * passed to the same async call).
236 *
237 * This function is for use by #TpProxy subclass implementations only, and
238 * should usually only be called from code generated by
239 * tools/glib-client-gen.py.
240 *
241 * Returns: a new pending call structure
242 *
243 * Since: 0.7.1
244 */
245 TpProxyPendingCall *
tp_proxy_pending_call_v0_new(TpProxy * self,GQuark iface,const gchar * member,DBusGProxy * iface_proxy,TpProxyInvokeFunc invoke_callback,GCallback callback,gpointer user_data,GDestroyNotify destroy,GObject * weak_object,gboolean cancel_must_raise)246 tp_proxy_pending_call_v0_new (TpProxy *self,
247 GQuark iface,
248 const gchar *member,
249 DBusGProxy *iface_proxy,
250 TpProxyInvokeFunc invoke_callback,
251 GCallback callback,
252 gpointer user_data,
253 GDestroyNotify destroy,
254 GObject *weak_object,
255 gboolean cancel_must_raise)
256 {
257 TpProxyPendingCall *pc;
258
259 g_return_val_if_fail (invoke_callback != NULL, NULL);
260 g_return_val_if_fail ((gpointer) iface_proxy != (gpointer) self, NULL);
261
262 pc = g_slice_new0 (TpProxyPendingCall);
263
264 MORE_DEBUG ("(proxy=%p, if=%s, meth=%s, ic=%p; cb=%p, ud=%p, dn=%p, wo=%p)"
265 " -> %p", self, g_quark_to_string (iface), member, invoke_callback,
266 callback, user_data, destroy, weak_object, pc);
267
268 pc->proxy = g_object_ref (self);
269 pc->invoke_callback = invoke_callback;
270 pc->callback = callback;
271 pc->user_data = user_data;
272 pc->destroy = destroy;
273 pc->weak_object = weak_object;
274 pc->iface_proxy = g_object_ref (iface_proxy);
275 pc->pending_call = NULL;
276 pc->priv = pending_call_magic;
277 pc->cancel_must_raise = cancel_must_raise;
278
279 if (weak_object != NULL)
280 g_object_weak_ref (weak_object, tp_proxy_pending_call_lost_weak_ref, pc);
281
282 g_signal_connect (iface_proxy, "destroy",
283 G_CALLBACK (_tp_proxy_pending_call_dgproxy_destroy), pc);
284
285 return pc;
286 }
287
288 /**
289 * tp_proxy_pending_call_cancel:
290 * @pc: a pending call
291 *
292 * Cancel the given pending call. After this function returns, you
293 * must not assume that the pending call remains valid, but you must not
294 * explicitly free it either.
295 *
296 * Since: 0.7.1
297 */
298 void
tp_proxy_pending_call_cancel(TpProxyPendingCall * pc)299 tp_proxy_pending_call_cancel (TpProxyPendingCall *pc)
300 {
301 DEBUG ("%p", pc);
302
303 g_return_if_fail (pc->priv == pending_call_magic);
304 g_return_if_fail (pc->proxy != NULL);
305 /* If the callback has already run, it's too late to cancel */
306 g_return_if_fail (!pc->idle_completed);
307
308 if (pc->cancel_must_raise)
309 {
310 if (pc->error != NULL)
311 g_error_free (pc->error);
312
313 pc->error = g_error_new_literal (TP_DBUS_ERRORS,
314 TP_DBUS_ERROR_CANCELLED, "Re-entrant D-Bus call cancelled");
315
316 if (pc->args != NULL)
317 {
318 tp_value_array_free (pc->args);
319 pc->args = NULL;
320 }
321 }
322 else
323 {
324 pc->invoke_callback = NULL;
325 }
326
327 /* If we're calling the callback due to cancellation, we must free the
328 * pending call object afterwards. Otherwise, we must free the pending
329 * call object later anyway, in case this function was called due to
330 * weak refs (like fd.o #14750). */
331 if (pc->idle_source == 0)
332 {
333 pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
334 tp_proxy_pending_call_idle_invoke, pc,
335 _tp_proxy_pending_call_idle_completed);
336 }
337
338 if (!pc->dbus_completed && pc->pending_call != NULL)
339 {
340 /* Implicitly asserts that iface_proxy is non-NULL */
341 DBusGProxy *iface_proxy = g_object_ref (pc->iface_proxy);
342
343 dbus_g_proxy_cancel_call (iface_proxy, pc->pending_call);
344 g_object_unref (iface_proxy);
345 }
346 }
347
348 static void
tp_proxy_pending_call_free(TpProxyPendingCall * pc)349 tp_proxy_pending_call_free (TpProxyPendingCall *pc)
350 {
351 MORE_DEBUG ("%p", pc);
352
353 g_assert (pc->priv == pending_call_magic);
354
355 if (pc->destroy != NULL)
356 pc->destroy (pc->user_data);
357
358 pc->destroy = NULL;
359 pc->user_data = NULL;
360
361 if (pc->error != NULL)
362 g_error_free (pc->error);
363
364 pc->error = NULL;
365
366 if (pc->args != NULL)
367 tp_value_array_free (pc->args);
368
369 pc->args = NULL;
370
371 if (pc->weak_object != NULL)
372 g_object_weak_unref (pc->weak_object,
373 tp_proxy_pending_call_lost_weak_ref, pc);
374
375 if (pc->iface_proxy != NULL)
376 {
377 g_signal_handlers_disconnect_by_func (pc->iface_proxy,
378 _tp_proxy_pending_call_dgproxy_destroy, pc);
379 g_object_unref (pc->iface_proxy);
380 pc->iface_proxy = NULL;
381 }
382
383 g_assert (pc->proxy != NULL);
384 g_object_unref (pc->proxy);
385 pc->proxy = NULL;
386
387 g_slice_free (TpProxyPendingCall, pc);
388 }
389
390 /**
391 * tp_proxy_pending_call_v0_completed:
392 * @p: a #TpProxyPendingCall allocated with tp_proxy_pending_call_v0_new()
393 *
394 * Indicate that dbus-glib has finished with this pending call, and therefore
395 * either tp_proxy_pending_call_v0_take_results() has already been called,
396 * or it will never be called. See tp_proxy_pending_call_v0_new().
397 *
398 * The signature is chosen to match #GDestroyNotify.
399 *
400 * This function is for use by #TpProxy subclass implementations only, and
401 * should usually only be called from code generated by
402 * tools/glib-client-gen.py.
403 *
404 * Since: 0.7.1
405 */
406 void
tp_proxy_pending_call_v0_completed(gpointer p)407 tp_proxy_pending_call_v0_completed (gpointer p)
408 {
409 TpProxyPendingCall *pc = p;
410
411 MORE_DEBUG ("%p", pc);
412
413 g_return_if_fail (pc->priv == pending_call_magic);
414 g_return_if_fail (!pc->dbus_completed);
415 g_return_if_fail (pc->proxy != NULL);
416
417 /* dbus-glib frees its user_data *before* it emits destroy; if we
418 * haven't yet queued the callback, assume that's what's going on. */
419 if (pc->idle_source == 0 && pc->iface_proxy != NULL)
420 {
421 MORE_DEBUG ("Looks like this pending call hasn't finished, assuming "
422 "the DBusGProxy is about to die");
423 /* this causes the pending call to be freed */
424 _tp_proxy_pending_call_dgproxy_destroy (pc->iface_proxy, pc);
425
426 g_assert (pc->iface_proxy == NULL);
427 }
428
429 pc->dbus_completed = TRUE;
430
431 /* If the idle callback has been run already, we can go away */
432 if (pc->idle_completed)
433 tp_proxy_pending_call_free (pc);
434 }
435
436 /**
437 * tp_proxy_pending_call_v0_take_pending_call:
438 * @pc: A pending call on which this function has not yet been called
439 * @pending_call: The underlying dbus-glib pending call
440 *
441 * Set the underlying pending call to be used by this object.
442 * See also tp_proxy_pending_call_v0_new().
443 *
444 * This function is for use by #TpProxy subclass implementations only, and
445 * should usually only be called from code generated by
446 * tools/glib-client-gen.py.
447 *
448 * Since: 0.7.1
449 */
450 void
tp_proxy_pending_call_v0_take_pending_call(TpProxyPendingCall * pc,DBusGProxyCall * pending_call)451 tp_proxy_pending_call_v0_take_pending_call (TpProxyPendingCall *pc,
452 DBusGProxyCall *pending_call)
453 {
454 g_return_if_fail (pc->priv == pending_call_magic);
455 g_return_if_fail (pc->pending_call == NULL);
456 g_return_if_fail (pc->proxy != NULL);
457
458 pc->pending_call = pending_call;
459 }
460
461 static void
_tp_proxy_pending_call_idle_completed(gpointer p)462 _tp_proxy_pending_call_idle_completed (gpointer p)
463 {
464 TpProxyPendingCall *pc = p;
465
466 MORE_DEBUG ("%p", pc);
467
468 pc->idle_completed = TRUE;
469
470 if (pc->dbus_completed)
471 tp_proxy_pending_call_free (pc);
472 }
473
474 /**
475 * tp_proxy_pending_call_v0_take_results:
476 * @pc: A pending call on which this function has not yet been called
477 * @error: %NULL if the call was successful, or an error (whose ownership
478 * is taken over by the pending call object). Because of dbus-glib
479 * idiosyncrasies, this must be the error produced by dbus-glib, not a copy.
480 * @args: %NULL if the call failed or had no "out" arguments, or an array
481 * of "out" arguments (whose ownership is taken over by the pending call
482 * object)
483 *
484 * Set the "out" arguments (return values) from this pending call.
485 * See also tp_proxy_pending_call_v0_new().
486 *
487 * This function is for use by #TpProxy subclass implementations only, and
488 * should usually only be called from code generated by
489 * tools/glib-client-gen.py.
490 *
491 * Since: 0.7.1
492 */
493 void
tp_proxy_pending_call_v0_take_results(TpProxyPendingCall * pc,GError * error,GValueArray * args)494 tp_proxy_pending_call_v0_take_results (TpProxyPendingCall *pc,
495 GError *error,
496 GValueArray *args)
497 {
498 g_return_if_fail (pc->proxy != NULL);
499 g_return_if_fail (pc->priv == pending_call_magic);
500 g_return_if_fail (pc->args == NULL);
501 g_return_if_fail (pc->error == NULL);
502 g_return_if_fail (pc->idle_source == 0);
503 g_return_if_fail (error == NULL || args == NULL);
504
505 MORE_DEBUG ("%p (error: %s)", pc,
506 error == NULL ? "(none)" : error->message);
507
508 pc->args = args;
509 pc->error = _tp_proxy_take_and_remap_error (pc->proxy, error);
510
511 /* queue up the actual callback to run after we go back to the event loop */
512 pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
513 tp_proxy_pending_call_idle_invoke, pc,
514 _tp_proxy_pending_call_idle_completed);
515 }
516