/*
* 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"
#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
/**
* TpProxySignalConnection:
*
* Opaque structure representing a D-Bus signal connection.
*
* Since: 0.7.1
*/
typedef struct _TpProxySignalInvocation TpProxySignalInvocation;
struct _TpProxySignalInvocation {
TpProxySignalConnection *sc;
TpProxy *proxy;
GValueArray *args;
guint idle_source;
};
struct _TpProxySignalConnection {
/* 1 if D-Bus has us
* 1 per member of @invocations
* 1 per callback being invoked right now */
gsize refcount;
/* borrowed ref (discarded when we see invalidated signal)
* + 1 per member of @invocations
* + 1 per callback being invoked (possibly nested!) right now */
TpProxy *proxy;
DBusGProxy *iface_proxy;
gchar *member;
GCallback collect_args;
TpProxyInvokeFunc invoke_callback;
GCallback callback;
gpointer user_data;
GDestroyNotify destroy;
GObject *weak_object;
/* queue of _TpProxySignalInvocation, not including any that are
* being invoked right now */
GQueue invocations;
};
static void _tp_proxy_signal_connection_dgproxy_destroy (DBusGProxy *,
TpProxySignalConnection *);
static void
tp_proxy_signal_connection_disconnect_dbus_glib (TpProxySignalConnection *sc)
{
DBusGProxy *iface_proxy = sc->iface_proxy;
/* ignore if already done */
if (iface_proxy == NULL)
return;
sc->iface_proxy = NULL;
g_signal_handlers_disconnect_by_func (iface_proxy,
_tp_proxy_signal_connection_dgproxy_destroy, sc);
dbus_g_proxy_disconnect_signal (iface_proxy, sc->member,
sc->collect_args, (gpointer) sc);
g_object_unref (iface_proxy);
}
static void
tp_proxy_signal_connection_proxy_invalidated (TpProxy *proxy,
guint domain,
gint code,
const gchar *message,
TpProxySignalConnection *sc)
{
g_assert (sc != NULL);
g_assert (domain != 0);
g_assert (message != NULL);
DEBUG ("%p: TpProxy %p invalidated (I have %p): %s", sc, proxy,
sc->proxy, message);
g_assert (proxy == sc->proxy);
g_signal_handlers_disconnect_by_func (sc->proxy,
tp_proxy_signal_connection_proxy_invalidated, sc);
sc->proxy = NULL;
tp_proxy_signal_connection_disconnect_dbus_glib (sc);
}
static void
tp_proxy_signal_connection_lost_weak_ref (gpointer data,
GObject *dead)
{
TpProxySignalConnection *sc = data;
DEBUG ("%p: lost weak ref to %p", sc, dead);
g_assert (dead == sc->weak_object);
sc->weak_object = NULL;
tp_proxy_signal_connection_disconnect (sc);
}
static gboolean
_tp_proxy_signal_connection_finish_free (gpointer p)
{
TpProxySignalConnection *sc = p;
if (sc->weak_object != NULL)
{
g_object_weak_unref (sc->weak_object,
tp_proxy_signal_connection_lost_weak_ref, sc);
sc->weak_object = NULL;
}
g_slice_free (TpProxySignalConnection, sc);
return FALSE;
}
/* Return TRUE if it dies. */
static gboolean
tp_proxy_signal_connection_unref (TpProxySignalConnection *sc)
{
if (--(sc->refcount) > 0)
{
MORE_DEBUG ("%p: %" G_GSIZE_FORMAT " refs left", sc, sc->refcount);
return FALSE;
}
MORE_DEBUG ("removed last ref to %p", sc);
if (sc->proxy != NULL)
{
g_signal_handlers_disconnect_by_func (sc->proxy,
tp_proxy_signal_connection_proxy_invalidated, sc);
sc->proxy = NULL;
}
g_assert (sc->invocations.length == 0);
if (sc->destroy != NULL)
sc->destroy (sc->user_data);
sc->destroy = NULL;
sc->user_data = NULL;
g_free (sc->member);
/* We can't inline this here, because of fd.o #14750. If our signal
* connection gets destroyed by side-effects of something else losing a
* weak reference to the same object (e.g. a pending call whose weak
* object is the same as ours has the last ref to the TpProxy, causing
* invalidation when the weak object goes away) then we need to avoid dying
* til *our* weak-reference callback has run. So, don't actually free the
* signal connection until we've re-entered the main loop. */
g_idle_add_full (G_PRIORITY_HIGH, _tp_proxy_signal_connection_finish_free,
sc, NULL);
return TRUE;
}
/**
* tp_proxy_signal_connection_disconnect:
* @sc: a signal connection
*
* Disconnect the given signal connection. After this function returns, you
* must not assume that the signal connection remains valid, but you must not
* explicitly free it either.
*
* It is not safe to call this function if @sc has been disconnected already,
* which happens in each of these situations:
*
*
* the @weak_object used when @sc was created has been
* destroyed
* tp_proxy_signal_connection_disconnect has already been
* used
* the proxy has been invalidated
*
*
* Since: 0.7.1
*/
void
tp_proxy_signal_connection_disconnect (TpProxySignalConnection *sc)
{
TpProxySignalInvocation *invocation;
while ((invocation = g_queue_pop_head (&sc->invocations)) != NULL)
{
g_assert (invocation->sc == sc);
g_object_unref (invocation->proxy);
invocation->proxy = NULL;
invocation->sc = NULL;
g_source_remove (invocation->idle_source);
if (tp_proxy_signal_connection_unref (sc))
return;
}
tp_proxy_signal_connection_disconnect_dbus_glib (sc);
}
static void
tp_proxy_signal_invocation_free (gpointer p)
{
TpProxySignalInvocation *invocation = p;
if (invocation->sc != NULL)
{
/* this shouldn't really happen - it'll get run if the idle source
* is removed by something other than t_p_s_c_disconnect or
* t_p_s_i_run */
WARNING ("idle source removed by someone else");
g_queue_remove (&invocation->sc->invocations, invocation);
g_object_unref (invocation->proxy);
tp_proxy_signal_connection_unref (invocation->sc);
}
g_assert (invocation->proxy == NULL);
if (invocation->args != NULL)
tp_value_array_free (invocation->args);
g_slice_free (TpProxySignalInvocation, invocation);
}
static gboolean
tp_proxy_signal_invocation_run (gpointer p)
{
TpProxySignalInvocation *invocation = p;
TpProxySignalInvocation *popped = g_queue_pop_head
(&invocation->sc->invocations);
/* if GLib is running idle handlers in the wrong order, then we've lost */
MORE_DEBUG ("%p: popped %p", invocation->sc, popped);
g_assert (popped == invocation);
invocation->sc->invoke_callback (invocation->proxy, NULL,
invocation->args, invocation->sc->callback, invocation->sc->user_data,
invocation->sc->weak_object);
/* the invoke callback steals args */
invocation->args = NULL;
/* there's one ref to the proxy per queued invocation, to keep it
* alive */
MORE_DEBUG ("%p refcount-- due to %p run, sc=%p", invocation->proxy,
invocation, invocation->sc);
g_object_unref (invocation->proxy);
invocation->proxy = NULL;
tp_proxy_signal_connection_unref (invocation->sc);
invocation->sc = NULL;
return FALSE;
}
static void
tp_proxy_signal_connection_dropped (gpointer p,
GClosure *unused)
{
TpProxySignalConnection *sc = p;
MORE_DEBUG ("%p (%u invocations queued)", sc, sc->invocations.length);
tp_proxy_signal_connection_unref (sc);
}
static void
_tp_proxy_signal_connection_dgproxy_destroy (DBusGProxy *iface_proxy,
TpProxySignalConnection *sc)
{
g_assert (iface_proxy != NULL);
g_assert (sc != NULL);
g_assert (sc->iface_proxy == iface_proxy);
DEBUG ("%p: DBusGProxy %p invalidated", sc, iface_proxy);
sc->iface_proxy = NULL;
g_signal_handlers_disconnect_by_func (iface_proxy,
_tp_proxy_signal_connection_dgproxy_destroy, sc);
g_object_unref (iface_proxy);
}
static void
collect_none (DBusGProxy *dgproxy, TpProxySignalConnection *sc)
{
tp_proxy_signal_connection_v0_take_results (sc, NULL);
}
/**
* tp_proxy_signal_connection_v0_new:
* @self: a proxy
* @iface: a quark whose string value is the D-Bus interface
* @member: the name of the signal to which we're connecting
* @expected_types: an array of expected GTypes for the arguments, terminated
* by %G_TYPE_INVALID
* @collect_args: a callback to be given to dbus_g_proxy_connect_signal(),
* which must marshal the arguments into a #GValueArray and use them to call
* tp_proxy_signal_connection_v0_take_results(); this callback is not
* guaranteed to be called by future versions of telepathy-glib, which might
* be able to implement its functionality internally. If no arguments are
* expected at all (expected_types = { G_TYPE_INVALID }) then this callback
* should instead be %NULL
* @invoke_callback: a function which will be called with @error = %NULL,
* which should invoke @callback with @user_data, @weak_object and other
* appropriate arguments taken from @args
* @callback: user callback to be invoked by @invoke_callback
* @user_data: user-supplied data for the callback
* @destroy: user-supplied destructor for the data, which will be called
* when the signal connection is disconnected for any reason,
* or will be called before this function returns if an error occurs
* @weak_object: if not %NULL, a #GObject which will be weakly referenced by
* the signal connection - if it is destroyed, the signal connection will
* automatically be disconnected
* @error: If not %NULL, used to raise an error if %NULL is returned
*
* Allocate a new structure representing a signal connection, and connect to
* the signal, arranging for @invoke_callback to be called when it arrives.
*
* 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 signal connection structure, or %NULL if the proxy does not
* have the desired interface or has become invalid
*
* Since: 0.7.1
*/
TpProxySignalConnection *
tp_proxy_signal_connection_v0_new (TpProxy *self,
GQuark iface,
const gchar *member,
const GType *expected_types,
GCallback collect_args,
TpProxyInvokeFunc invoke_callback,
GCallback callback,
gpointer user_data,
GDestroyNotify destroy,
GObject *weak_object,
GError **error)
{
TpProxySignalConnection *sc;
DBusGProxy *iface_proxy = tp_proxy_get_interface_by_id (self,
iface, error);
if (iface_proxy == NULL)
{
if (destroy != NULL)
destroy (user_data);
return NULL;
}
if (expected_types[0] == G_TYPE_INVALID)
{
collect_args = G_CALLBACK (collect_none);
}
else
{
g_return_val_if_fail (collect_args != NULL, NULL);
}
sc = g_slice_new0 (TpProxySignalConnection);
MORE_DEBUG ("(proxy=%p, if=%s, sig=%s, collect=%p, invoke=%p, "
"cb=%p, ud=%p, dn=%p, wo=%p) -> %p",
self, g_quark_to_string (iface), member, collect_args,
invoke_callback, callback, user_data, destroy, weak_object, sc);
sc->refcount = 1;
sc->proxy = self;
sc->iface_proxy = g_object_ref (iface_proxy);
sc->member = g_strdup (member);
sc->collect_args = collect_args;
sc->invoke_callback = invoke_callback;
sc->callback = callback;
sc->user_data = user_data;
sc->destroy = destroy;
sc->weak_object = weak_object;
if (weak_object != NULL)
g_object_weak_ref (weak_object, tp_proxy_signal_connection_lost_weak_ref,
sc);
g_signal_connect (self, "invalidated",
G_CALLBACK (tp_proxy_signal_connection_proxy_invalidated), sc);
g_signal_connect (iface_proxy, "destroy",
G_CALLBACK (_tp_proxy_signal_connection_dgproxy_destroy), sc);
dbus_g_proxy_connect_signal (iface_proxy, member, collect_args, sc,
tp_proxy_signal_connection_dropped);
return sc;
}
/**
* tp_proxy_signal_connection_v0_take_results:
* @sc: The signal connection
* @args: The arguments of the signal
*
* Feed the results of a signal invocation back into the signal connection
* machinery.
*
* This method should only be called from #TpProxy subclass implementations,
* in the callback that implements @collect_args.
*
* Since: 0.7.1
*/
void
tp_proxy_signal_connection_v0_take_results (TpProxySignalConnection *sc,
GValueArray *args)
{
TpProxySignalInvocation *invocation = g_slice_new0 (TpProxySignalInvocation);
/* FIXME: assert that the GValueArray is the right length, or
* even that it contains the right types? */
/* as long as there are queued invocations, we keep one ref to the TpProxy
* and one ref to the TpProxySignalConnection per invocation */
MORE_DEBUG ("%p refcount++ due to %p, sc=%p", sc->proxy, invocation, sc);
invocation->proxy = g_object_ref (sc->proxy);
sc->refcount++;
invocation->sc = sc;
invocation->args = args;
g_queue_push_tail (&sc->invocations, invocation);
MORE_DEBUG ("invocations: head=%p tail=%p count=%u",
sc->invocations.head, sc->invocations.tail,
sc->invocations.length);
invocation->idle_source = g_idle_add_full (G_PRIORITY_HIGH,
tp_proxy_signal_invocation_run, invocation,
tp_proxy_signal_invocation_free);
}