/*
* Copyright (C) 2007-2008 Collabora Ltd.
* Copyright (C) 2007-2008 Nokia Corporation
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "config.h"
#include "telepathy-glib/proxy-subclass.h"
#include "telepathy-glib/proxy-internal.h"
#define DEBUG_FLAG TP_DEBUG_PROXY
#include "telepathy-glib/debug-internal.h"
#include
#if 0
#define MORE_DEBUG DEBUG
#else
#define MORE_DEBUG(...) G_STMT_START {} G_STMT_END
#endif
/**
* TpProxyPendingCall:
*
* Opaque structure representing a pending D-Bus call.
*
* Since: 0.7.1
*/
struct _TpProxyPendingCall {
/* This structure's "reference count" is implicit:
* - 1 if D-Bus has us (from creation until _completed)
* - 1 if results have come in but we haven't run the callback yet
* (idle_source is nonzero)
*
* In normal use, its life cycle should go like this:
* - Created by tp_proxy_pending_call_v0_new
* - Given to dbus-glib by generated code (actual call starts here)
* - tp_proxy_pending_call_v0_take_pending_call
* - (Phase 1)
* - tp_proxy_pending_call_v0_take_results
* - Idle handler queued
* - (Phase 2)
* - tp_proxy_pending_call_v0_completed
* - (Phase 3)
* - tp_proxy_pending_call_idle_invoke
* - tp_proxy_pending_call_free
*
* although we can't guarantee that idle_invoke won't go off before
* completed does, if the dbus-glib implementation changes.
*
* Exceptional conditions that can occur:
* - Weak object dies
* - Reference cleared, otherwise equivalent to explicit cancellation
* - Explicitly cancelled
* - All phases: callback invoked if cancel_must_raise, otherwise
* not
* - DBusGProxy destroy signal (or _completed before _take_results)
* - Phase 1: error callback queued
* - Phase 2: ignored, we use the results we've already got
* - Phase 3: ignored, we use the results we've already got
*/
/* Always non-NULL */
TpProxy *proxy;
/* Set to NULL after it's been invoked once, or if cancellation means
* it should never be called. Supplied by the generated code */
TpProxyInvokeFunc invoke_callback;
/* arguments for invoke_callback supplied by _take_results, by
* cancellation or by the destroy signal */
GError *error /* implicitly initialized */;
GValueArray *args;
/* user-supplied arguments for invoke_callback */
GCallback callback;
gpointer user_data;
GDestroyNotify destroy;
GObject *weak_object;
/* Non-NULL until either _completed or destroy, whichever comes first */
DBusGProxy *iface_proxy;
DBusGProxyCall *pending_call;
/* Nonzero if _idle_invoke has been queued (even if it has already
* happened), i.e. if results have been taken or the DBusGProxy
* was destroyed */
guint idle_source;
/* If TRUE, invoke the callback even on cancellation */
unsigned cancel_must_raise:1;
/* If TRUE, the idle_invoke callback has either run or been cancelled */
unsigned idle_completed:1;
/* If TRUE, dbus-glib no longer holds a reference to us */
unsigned dbus_completed:1;
/* Marker to indicate that this is, in fact, a valid TpProxyPendingCall */
gconstpointer priv;
};
static const gchar * const pending_call_magic = "TpProxyPendingCall";
static void
tp_proxy_pending_call_lost_weak_ref (gpointer data,
GObject *dead)
{
TpProxyPendingCall *pc = data;
DEBUG ("%p lost weak ref to %p", pc, dead);
g_assert (pc->priv == pending_call_magic);
g_assert (dead == pc->weak_object);
pc->weak_object = NULL;
if (!pc->idle_completed)
tp_proxy_pending_call_cancel (pc);
}
static gboolean
tp_proxy_pending_call_idle_invoke (gpointer p)
{
TpProxyPendingCall *pc = p;
TpProxyInvokeFunc invoke = pc->invoke_callback;
MORE_DEBUG ("%p", pc);
if (invoke == NULL)
{
/* either already invoked (bug?), or cancelled */
return FALSE;
}
MORE_DEBUG ("%p: invoking user callback", pc);
g_assert (pc->proxy != NULL);
g_assert (pc->error == NULL || pc->args == NULL);
g_assert (!pc->idle_completed);
pc->invoke_callback = NULL;
invoke (pc->proxy, pc->error, pc->args, pc->callback,
pc->user_data, pc->weak_object);
pc->error = NULL;
pc->args = NULL;
/* don't clear pc->idle_source here! tp_proxy_pending_call_v0_completed
* compares it to 0 to determine whether to free the object */
return FALSE;
}
static void _tp_proxy_pending_call_idle_completed (gpointer p);
static void
_tp_proxy_pending_call_dgproxy_destroy (DBusGProxy *iface_proxy,
TpProxyPendingCall *pc)
{
g_assert (iface_proxy != NULL);
g_assert (pc != NULL);
g_assert (pc->iface_proxy == iface_proxy);
g_assert (pc->proxy != NULL);
DEBUG ("%p: DBusGProxy %p invalidated", pc, iface_proxy);
if (pc->idle_source == 0)
{
/* we haven't already received and queued a reply, so synthesize
* one */
g_assert (pc->args == NULL);
g_assert (pc->error == NULL);
pc->error = g_error_new_literal (TP_DBUS_ERRORS,
TP_DBUS_ERROR_NAME_OWNER_LOST, "Name owner lost (service crashed?)");
pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
tp_proxy_pending_call_idle_invoke, pc,
_tp_proxy_pending_call_idle_completed);
}
g_signal_handlers_disconnect_by_func (pc->iface_proxy,
_tp_proxy_pending_call_dgproxy_destroy, pc);
g_object_unref (pc->iface_proxy);
pc->iface_proxy = NULL;
}
/**
* tp_proxy_pending_call_v0_new:
* @self: a proxy
* @iface: a quark whose string value is the D-Bus interface
* @member: the name of the method being called
* @iface_proxy: the interface-specific #DBusGProxy for @iface
* @invoke_callback: an implementation of #TpProxyInvokeFunc which will
* invoke @callback with appropriate arguments
* @callback: a callback to be called when the call completes
* @user_data: user-supplied data for the callback
* @destroy: user-supplied destructor for the data
* @weak_object: if not %NULL, a #GObject which will be weakly referenced by
* the signal connection - if it is destroyed, the pending call will
* automatically be cancelled
* @cancel_must_raise: if %TRUE, the @invoke_callback will be run with
* error %TP_DBUS_ERROR_CANCELLED if the call is cancelled by a call to
* tp_proxy_pending_call_cancel() or by destruction of the @weak_object;
* if %FALSE, the @invoke_callback will not be run at all in these cases
*
* Allocate a new pending call structure. After calling this function, the
* caller must start an asynchronous D-Bus call and give the resulting
* DBusGProxyCall to the pending call object using
* tp_proxy_pending_call_v0_take_pending_call().
*
* If dbus-glib gets a reply to the call before it's cancelled, the caller
* must arrange for tp_proxy_pending_call_v0_take_results() to be called
* with the results (the intention is for this to be done immediately
* after dbus_g_proxy_end_call in the callback supplied to dbus-glib).
*
* When dbus-glib discards its reference to the user_data supplied in the
* asynchronous D-Bus call (i.e. after the call is cancelled or a reply
* arrives), tp_proxy_pending_call_v0_completed must be called (the intention
* is for the #TpProxyPendingCall to be the @user_data in the async call,
* and for tp_proxy_pending_call_v0_completed to be the #GDestroyNotify
* passed to the same async call).
*
* This function is for use by #TpProxy subclass implementations only, and
* should usually only be called from code generated by
* tools/glib-client-gen.py.
*
* Returns: a new pending call structure
*
* Since: 0.7.1
*/
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)
{
TpProxyPendingCall *pc;
g_return_val_if_fail (invoke_callback != NULL, NULL);
g_return_val_if_fail ((gpointer) iface_proxy != (gpointer) self, NULL);
pc = g_slice_new0 (TpProxyPendingCall);
MORE_DEBUG ("(proxy=%p, if=%s, meth=%s, ic=%p; cb=%p, ud=%p, dn=%p, wo=%p)"
" -> %p", self, g_quark_to_string (iface), member, invoke_callback,
callback, user_data, destroy, weak_object, pc);
pc->proxy = g_object_ref (self);
pc->invoke_callback = invoke_callback;
pc->callback = callback;
pc->user_data = user_data;
pc->destroy = destroy;
pc->weak_object = weak_object;
pc->iface_proxy = g_object_ref (iface_proxy);
pc->pending_call = NULL;
pc->priv = pending_call_magic;
pc->cancel_must_raise = cancel_must_raise;
if (weak_object != NULL)
g_object_weak_ref (weak_object, tp_proxy_pending_call_lost_weak_ref, pc);
g_signal_connect (iface_proxy, "destroy",
G_CALLBACK (_tp_proxy_pending_call_dgproxy_destroy), pc);
return pc;
}
/**
* tp_proxy_pending_call_cancel:
* @pc: a pending call
*
* Cancel the given pending call. After this function returns, you
* must not assume that the pending call remains valid, but you must not
* explicitly free it either.
*
* Since: 0.7.1
*/
void
tp_proxy_pending_call_cancel (TpProxyPendingCall *pc)
{
DEBUG ("%p", pc);
g_return_if_fail (pc->priv == pending_call_magic);
g_return_if_fail (pc->proxy != NULL);
/* If the callback has already run, it's too late to cancel */
g_return_if_fail (!pc->idle_completed);
if (pc->cancel_must_raise)
{
if (pc->error != NULL)
g_error_free (pc->error);
pc->error = g_error_new_literal (TP_DBUS_ERRORS,
TP_DBUS_ERROR_CANCELLED, "Re-entrant D-Bus call cancelled");
if (pc->args != NULL)
{
tp_value_array_free (pc->args);
pc->args = NULL;
}
}
else
{
pc->invoke_callback = NULL;
}
/* If we're calling the callback due to cancellation, we must free the
* pending call object afterwards. Otherwise, we must free the pending
* call object later anyway, in case this function was called due to
* weak refs (like fd.o #14750). */
if (pc->idle_source == 0)
{
pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
tp_proxy_pending_call_idle_invoke, pc,
_tp_proxy_pending_call_idle_completed);
}
if (!pc->dbus_completed && pc->pending_call != NULL)
{
/* Implicitly asserts that iface_proxy is non-NULL */
DBusGProxy *iface_proxy = g_object_ref (pc->iface_proxy);
dbus_g_proxy_cancel_call (iface_proxy, pc->pending_call);
g_object_unref (iface_proxy);
}
}
static void
tp_proxy_pending_call_free (TpProxyPendingCall *pc)
{
MORE_DEBUG ("%p", pc);
g_assert (pc->priv == pending_call_magic);
if (pc->destroy != NULL)
pc->destroy (pc->user_data);
pc->destroy = NULL;
pc->user_data = NULL;
if (pc->error != NULL)
g_error_free (pc->error);
pc->error = NULL;
if (pc->args != NULL)
tp_value_array_free (pc->args);
pc->args = NULL;
if (pc->weak_object != NULL)
g_object_weak_unref (pc->weak_object,
tp_proxy_pending_call_lost_weak_ref, pc);
if (pc->iface_proxy != NULL)
{
g_signal_handlers_disconnect_by_func (pc->iface_proxy,
_tp_proxy_pending_call_dgproxy_destroy, pc);
g_object_unref (pc->iface_proxy);
pc->iface_proxy = NULL;
}
g_assert (pc->proxy != NULL);
g_object_unref (pc->proxy);
pc->proxy = NULL;
g_slice_free (TpProxyPendingCall, pc);
}
/**
* tp_proxy_pending_call_v0_completed:
* @p: a #TpProxyPendingCall allocated with tp_proxy_pending_call_v0_new()
*
* Indicate that dbus-glib has finished with this pending call, and therefore
* either tp_proxy_pending_call_v0_take_results() has already been called,
* or it will never be called. See tp_proxy_pending_call_v0_new().
*
* The signature is chosen to match #GDestroyNotify.
*
* This function is for use by #TpProxy subclass implementations only, and
* should usually only be called from code generated by
* tools/glib-client-gen.py.
*
* Since: 0.7.1
*/
void
tp_proxy_pending_call_v0_completed (gpointer p)
{
TpProxyPendingCall *pc = p;
MORE_DEBUG ("%p", pc);
g_return_if_fail (pc->priv == pending_call_magic);
g_return_if_fail (!pc->dbus_completed);
g_return_if_fail (pc->proxy != NULL);
/* dbus-glib frees its user_data *before* it emits destroy; if we
* haven't yet queued the callback, assume that's what's going on. */
if (pc->idle_source == 0 && pc->iface_proxy != NULL)
{
MORE_DEBUG ("Looks like this pending call hasn't finished, assuming "
"the DBusGProxy is about to die");
/* this causes the pending call to be freed */
_tp_proxy_pending_call_dgproxy_destroy (pc->iface_proxy, pc);
g_assert (pc->iface_proxy == NULL);
}
pc->dbus_completed = TRUE;
/* If the idle callback has been run already, we can go away */
if (pc->idle_completed)
tp_proxy_pending_call_free (pc);
}
/**
* tp_proxy_pending_call_v0_take_pending_call:
* @pc: A pending call on which this function has not yet been called
* @pending_call: The underlying dbus-glib pending call
*
* Set the underlying pending call to be used by this object.
* See also tp_proxy_pending_call_v0_new().
*
* This function is for use by #TpProxy subclass implementations only, and
* should usually only be called from code generated by
* tools/glib-client-gen.py.
*
* Since: 0.7.1
*/
void
tp_proxy_pending_call_v0_take_pending_call (TpProxyPendingCall *pc,
DBusGProxyCall *pending_call)
{
g_return_if_fail (pc->priv == pending_call_magic);
g_return_if_fail (pc->pending_call == NULL);
g_return_if_fail (pc->proxy != NULL);
pc->pending_call = pending_call;
}
static void
_tp_proxy_pending_call_idle_completed (gpointer p)
{
TpProxyPendingCall *pc = p;
MORE_DEBUG ("%p", pc);
pc->idle_completed = TRUE;
if (pc->dbus_completed)
tp_proxy_pending_call_free (pc);
}
/**
* tp_proxy_pending_call_v0_take_results:
* @pc: A pending call on which this function has not yet been called
* @error: %NULL if the call was successful, or an error (whose ownership
* is taken over by the pending call object). Because of dbus-glib
* idiosyncrasies, this must be the error produced by dbus-glib, not a copy.
* @args: %NULL if the call failed or had no "out" arguments, or an array
* of "out" arguments (whose ownership is taken over by the pending call
* object)
*
* Set the "out" arguments (return values) from this pending call.
* See also tp_proxy_pending_call_v0_new().
*
* This function is for use by #TpProxy subclass implementations only, and
* should usually only be called from code generated by
* tools/glib-client-gen.py.
*
* Since: 0.7.1
*/
void
tp_proxy_pending_call_v0_take_results (TpProxyPendingCall *pc,
GError *error,
GValueArray *args)
{
g_return_if_fail (pc->proxy != NULL);
g_return_if_fail (pc->priv == pending_call_magic);
g_return_if_fail (pc->args == NULL);
g_return_if_fail (pc->error == NULL);
g_return_if_fail (pc->idle_source == 0);
g_return_if_fail (error == NULL || args == NULL);
MORE_DEBUG ("%p (error: %s)", pc,
error == NULL ? "(none)" : error->message);
pc->args = args;
pc->error = _tp_proxy_take_and_remap_error (pc->proxy, error);
/* queue up the actual callback to run after we go back to the event loop */
pc->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
tp_proxy_pending_call_idle_invoke, pc,
_tp_proxy_pending_call_idle_completed);
}