/* * proxy.c - Base class for Telepathy client proxies * * 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" #include #include #include #include #include "dbus-internal.h" #define DEBUG_FLAG TP_DEBUG_PROXY #include "debug-internal.h" #include "simple-client-factory-internal.h" #include "util-internal.h" #include "_gen/tp-cli-generic-body.h" #if 0 #define MORE_DEBUG DEBUG #else #define MORE_DEBUG(...) G_STMT_START {} G_STMT_END #endif /** * TP_DBUS_ERRORS: * * #GError domain representing D-Bus errors not directly related to * Telepathy, for use by #TpProxy. The @code in a #GError with this * domain must be a member of #TpDBusError. * * This macro expands to a function call returning a #GQuark. * * Since: 0.7.1 */ GQuark tp_dbus_errors_quark (void) { static GQuark q = 0; if (q == 0) q = g_quark_from_static_string ("tp_dbus_errors_quark"); return q; } /** * TpDBusError: * @TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR: Raised if the error raised by * a remote D-Bus object is not recognised * @TP_DBUS_ERROR_PROXY_UNREFERENCED: Emitted in #TpProxy::invalidated * when the #TpProxy has lost its last reference * @TP_DBUS_ERROR_NO_INTERFACE: Raised by #TpProxy methods if the remote * object does not appear to have the required interface * @TP_DBUS_ERROR_NAME_OWNER_LOST: Emitted in #TpProxy::invalidated if the * remote process loses ownership of its bus name, and raised by * any #TpProxy methods that have not had a reply at that time or are called * after the proxy becomes invalid in this way (usually meaning it crashed) * @TP_DBUS_ERROR_INVALID_BUS_NAME: Raised if a D-Bus bus name given is not * valid, or is of an unacceptable type (e.g. well-known vs. unique) * @TP_DBUS_ERROR_INVALID_INTERFACE_NAME: Raised if a D-Bus interface or * error name given is not valid * @TP_DBUS_ERROR_INVALID_OBJECT_PATH: Raised if a D-Bus object path * given is not valid * @TP_DBUS_ERROR_INVALID_MEMBER_NAME: Raised if a D-Bus method or signal * name given is not valid * @TP_DBUS_ERROR_OBJECT_REMOVED: A generic error which can be used with * #TpProxy::invalidated to indicate an application-specific indication * that the remote object no longer exists, if no more specific error * is available. * @TP_DBUS_ERROR_CANCELLED: Raised from calls that re-enter the main * loop (*_run_*) if they are cancelled * @TP_DBUS_ERROR_INCONSISTENT: Raised if information received from a remote * object is inconsistent or otherwise obviously wrong (added in 0.7.17). * See also %TP_ERROR_CONFUSED. * * #GError codes for use with the %TP_DBUS_ERRORS domain. * * Since 0.11.5, there is a corresponding #GEnumClass type, * %TP_TYPE_DBUS_ERROR. * * Since: 0.7.1 */ /** * NUM_TP_DBUS_ERRORS: (skip) * * 1 more than the highest valid #TpDBusError at the time of compilation. * In new code, use %TP_NUM_DBUS_ERRORS instead. * * Since: 0.7.1 */ /** * TP_NUM_DBUS_ERRORS: * * 1 more than the highest valid #TpDBusError at the time of compilation * * Since: 0.19.0 */ /** * TP_TYPE_DBUS_ERROR: * * The #GEnumClass type of a #TpDBusError. * * Since: 0.11.5 */ /** * SECTION:proxy * @title: TpProxy * @short_description: base class for Telepathy client proxy objects * @see_also: #TpChannel, #TpConnection, #TpConnectionManager * * #TpProxy is a base class for Telepathy client-side proxies, which represent * an object accessed via D-Bus and provide access to its methods and signals. * * Since: 0.7.1 */ /** * SECTION:proxy-dbus-core * @title: TpProxy D-Bus core methods * @short_description: The D-Bus Introspectable, Peer and Properties interfaces * @see_also: #TpProxy * * All D-Bus objects support the Peer interface, and many support the * Introspectable and Properties interfaces. * * Since: 0.7.2 */ /** * SECTION:proxy-tp-properties * @title: TpProxy Telepathy Properties * @short_description: The Telepathy Properties interface * @see_also: #TpProxy * * As well as #TpProxy, proxy.h includes auto-generated client wrappers for the * Telepathy Properties interface, which can be implemented by any type of * object. * * The Telepathy Properties interface should not be confused with the D-Bus * core Properties interface. * * Since: 0.7.1 */ /** * SECTION:proxy-subclass * @title: TpProxy subclasses and mixins * @short_description: Providing extra functionality for a #TpProxy or * subclass, or subclassing it * @see_also: #TpProxy * * The implementations of #TpProxy subclasses and "mixin" functions need * access to the underlying dbus-glib objects used to implement the * #TpProxy API. * * Mixin functions to implement particular D-Bus interfaces should usually * be auto-generated, by copying tools/glib-client-gen.py from telepathy-glib. * * Since: 0.7.1 */ /** * TpProxy: * * Structure representing a Telepathy client-side proxy. * * Since: 0.7.1 */ /** * TpProxyInterfaceAddedCb: * @self: the proxy * @quark: a quark whose string value is the interface being added * @proxy: the #DBusGProxy for the added interface * @unused: unused * * The signature of a #TpProxy::interface-added signal callback. * * Since: 0.7.1 */ /** * TpProxyClass: * @parent_class: The parent class structure * @interface: If set non-zero by a subclass, #TpProxy will * automatically add this interface in its constructor * @must_have_unique_name: If set %TRUE by a subclass, the #TpProxy * constructor will fail if a well-known bus name is given * * The class of a #TpProxy. The struct fields not documented here are reserved. * * Since: 0.7.1 */ /** * TpProxyPrepareAsync: * @proxy: the object on which @feature has to be prepared * @feature: a #GQuark representing the feature to prepare * @callback: called when the feature has been prepared, or the preparation * failed * @user_data: data to pass to @callback * * Function called when @feature has to be prepared for @proxy. */ /** * TpProxyFeature: * @name: a #GQuark representing the name of the feature * @core: if %TRUE, every non-core feature of the class depends on this one, * and every feature (core or not) in subclasses depends on this one * @prepare_async: called when the feature has to be prepared * @prepare_before_signalling_connected_async: only relevant for * TpConnection sub-classes; same as @prepare_async but for * features wanting to have a chance to prepare themself before the * TpConnection object announce its %TP_CONNECTION_STATUS_CONNECTED status * @interfaces_needed: an array of #GQuark representing interfaces which have * to be implemented on the object in order to be able to prepare the feature * @depends_on: an array of #GQuark representing other features which have to * be prepared before trying to prepare this feature * @can_retry: If %TRUE, allow retrying preparation of this feature even if it * failed once already; if %FALSE any attempt of preparing the feature after * the preparation already failed once will immediately fail with re-calling * @prepare_async * * Structure representing a feature. * * Since: 0.11.3 */ /** * TpProxyClassFeatureListFunc: * @cls: a subclass of #TpProxyClass * * A function called to list the features supported by * tp_proxy_prepare_async(). Currently, only code inside telepathy-glib can * implement this. * * Returns: an array of feature descriptions * * Since: 0.11.3 */ typedef struct _TpProxyErrorMappingLink TpProxyErrorMappingLink; struct _TpProxyErrorMappingLink { const gchar *prefix; GQuark domain; GEnumClass *code_enum_class; TpProxyErrorMappingLink *next; }; typedef struct _TpProxyInterfaceAddLink TpProxyInterfaceAddLink; struct _TpProxyInterfaceAddLink { TpProxyInterfaceAddedCb callback; TpProxyInterfaceAddLink *next; }; struct _TpProxyFeaturePrivate { gpointer unused; }; /** * TpProxyInvokeFunc: * @self: the #TpProxy on which the D-Bus method was invoked * @error: %NULL if the method call succeeded, or a non-%NULL error if the * method call failed * @args: array of "out" arguments (return values) for the D-Bus method, * or %NULL if an error occurred or if there were no "out" arguments * @callback: the callback that should be invoked, as passed to * tp_proxy_pending_call_v0_new() * @user_data: user-supplied data to pass to the callback, as passed to * tp_proxy_pending_call_v0_new() * @weak_object: user-supplied object to pass to the callback, as passed to * tp_proxy_pending_call_v0_new() * * Signature of a callback invoked by the #TpProxy machinery after a D-Bus * method call has succeeded or failed. It is responsible for calling the * user-supplied callback. * * Because parts of dbus-glib aren't reentrant, this callback may be called * from an idle handler shortly after the method call reply is received, * rather than from the callback for the reply. * * At most one of @args and @error can be non-%NULL (implementations may * assert this). @args and @error may both be %NULL if a method with no * "out" arguments (i.e. a method that returns nothing) was called * successfully. * * The #TpProxyInvokeFunc must call callback with @user_data, @weak_object, * and appropriate arguments derived from @error and @args. It is responsible * for freeing @error and @args, if their ownership has not been transferred. * * Since: 0.7.1 */ typedef enum { /* Not a feature */ FEATURE_STATE_INVALID = GPOINTER_TO_INT (NULL), /* Nobody cares */ FEATURE_STATE_UNWANTED, /* Want to prepare, waiting for dependencies to be satisfied (or maybe * just poll_features being called) */ FEATURE_STATE_WANTED, /* Want to prepare, have called prepare_async */ FEATURE_STATE_TRYING, /* Couldn't prepare because a required interface on the connection * was missing and the connection wasn't connected yet. We'll retry to * prepare once the connection is connected. * This state is only used when preparing Connection features */ FEATURE_STATE_MISSING_IFACE, /* Couldn't prepare, gave up */ FEATURE_STATE_FAILED, /* Prepared */ FEATURE_STATE_READY } FeatureState; typedef struct { GSimpleAsyncResult *result; GArray *features; gboolean core; } TpProxyPrepareRequest; static TpProxyPrepareRequest * tp_proxy_prepare_request_new (GSimpleAsyncResult *result, const GQuark *features) { TpProxyPrepareRequest *req = g_slice_new0 (TpProxyPrepareRequest); if (result != NULL) req->result = g_object_ref (result); req->features = _tp_quark_array_copy (features); g_assert (req->features != NULL); return req; } static void tp_proxy_prepare_request_finish (TpProxyPrepareRequest *req, const GError *error) { DEBUG ("%p", req); if (req->result != NULL) { if (error != NULL) g_simple_async_result_set_from_error (req->result, error); g_simple_async_result_complete_in_idle (req->result); g_object_unref (req->result); } g_array_unref (req->features); g_slice_free (TpProxyPrepareRequest, req); } struct _TpProxyPrivate { /* GQuark for interface => either a ref'd DBusGProxy *, * or the TpProxy itself used as a dummy value to indicate that * the DBusGProxy has not been needed yet */ GData *interfaces; /* feature => FeatureState */ GData *features; /* Queue of TpProxyPrepareRequest. The first requests are the core one, * sorted from the most upper super class to the subclass core features. * This is needed to guarantee than subclass features are not prepared * until the super class features have been prepared. */ GQueue *prepare_requests; GSimpleAsyncResult *will_announce_connected_result; /* Number of pending calls blocking will_announce_connected_result to be * completed */ guint pending_will_announce_calls; gboolean dispose_has_run; TpSimpleClientFactory *factory; }; G_DEFINE_TYPE (TpProxy, tp_proxy, G_TYPE_OBJECT) enum { PROP_DBUS_DAEMON = 1, PROP_DBUS_CONNECTION, PROP_BUS_NAME, PROP_OBJECT_PATH, PROP_INTERFACES, PROP_FACTORY, N_PROPS }; enum { SIGNAL_INTERFACE_ADDED, SIGNAL_INVALIDATED, N_SIGNALS }; static guint signals[N_SIGNALS] = {0}; static void tp_proxy_iface_destroyed_cb (DBusGProxy *dgproxy, TpProxy *self); /** * tp_proxy_borrow_interface_by_id: (skip) * @self: the TpProxy * @iface: quark representing the interface required * @error: used to raise an error in the #TP_DBUS_ERRORS domain if @iface * is invalid, @self has been invalidated or @self does not implement * @iface * * * * Returns: a borrowed reference to a #DBusGProxy * for which the bus name and object path are the same as for @self, but the * interface is as given (or %NULL if an @error is raised). * The reference is only valid as long as @self is. * * Since: 0.7.1 * Deprecated: Since 0.19.9. New code should use * tp_proxy_get_interface_by_id() instead. */ DBusGProxy * tp_proxy_borrow_interface_by_id (TpProxy *self, GQuark iface, GError **error) { return tp_proxy_get_interface_by_id (self, iface, error); } /** * tp_proxy_get_interface_by_id: (skip) * @self: the TpProxy * @iface: quark representing the interface required * @error: used to raise an error in the #TP_DBUS_ERRORS domain if @iface * is invalid, @self has been invalidated or @self does not implement * @iface * * * * Returns: a borrowed reference to a #DBusGProxy * for which the bus name and object path are the same as for @self, but the * interface is as given (or %NULL if an @error is raised). * The reference is only valid as long as @self is. * * Since: 0.19.9 */ DBusGProxy * tp_proxy_get_interface_by_id (TpProxy *self, GQuark iface, GError **error) { gpointer dgproxy; if (self->invalidated != NULL) { g_set_error (error, self->invalidated->domain, self->invalidated->code, "%s", self->invalidated->message); return NULL; } if (!tp_dbus_check_valid_interface_name (g_quark_to_string (iface), error)) return NULL; dgproxy = g_datalist_id_get_data (&self->priv->interfaces, iface); if (dgproxy == self) { /* dummy value - we've never actually needed the interface, so we * didn't create it, to avoid binding to all the signals */ dgproxy = dbus_g_proxy_new_for_name (self->dbus_connection, self->bus_name, self->object_path, g_quark_to_string (iface)); DEBUG ("%p: %s DBusGProxy is %p", self, g_quark_to_string (iface), dgproxy); g_signal_connect (dgproxy, "destroy", G_CALLBACK (tp_proxy_iface_destroyed_cb), self); g_datalist_id_set_data_full (&self->priv->interfaces, iface, dgproxy, g_object_unref); g_signal_emit (self, signals[SIGNAL_INTERFACE_ADDED], 0, (guint) iface, dgproxy); } if (dgproxy != NULL) { return dgproxy; } g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_NO_INTERFACE, "Object %s does not have interface %s", self->object_path, g_quark_to_string (iface)); return NULL; } /** * tp_proxy_has_interface_by_id: * @self: the #TpProxy (or subclass) * @iface: quark representing the D-Bus interface required * * Return whether this proxy is known to have a particular interface, by its * quark ID. This is equivalent to using g_quark_to_string() followed by * tp_proxy_has_interface(), but more efficient. * * Returns: %TRUE if this proxy implements the given interface. * * Since: 0.7.1 */ gboolean tp_proxy_has_interface_by_id (gpointer self, GQuark iface) { TpProxy *proxy = self; g_return_val_if_fail (TP_IS_PROXY (self), FALSE); return (g_datalist_id_get_data (&proxy->priv->interfaces, iface) != NULL); } /** * tp_proxy_has_interface: * @self: the #TpProxy (or subclass) * @iface: the D-Bus interface required, as a string * * Return whether this proxy is known to have a particular interface. In * versions older than 0.11.11, this was a macro wrapper around * tp_proxy_has_interface_by_id(). * * For objects that discover their interfaces at runtime, this method will * indicate that interfaces are missing until they are known to be present. * In subclasses that define features for use with tp_proxy_prepare_async(), * successfully preparing the "core" feature for that subclass (such as * %TP_CHANNEL_FEATURE_CORE or %TP_CONNECTION_FEATURE_CORE) implies that the * interfaces are known. * * Returns: %TRUE if this proxy implements the given interface. * Since: 0.7.1 */ gboolean tp_proxy_has_interface (gpointer self, const gchar *iface) { TpProxy *proxy = self; GQuark q = g_quark_try_string (iface); g_return_val_if_fail (TP_IS_PROXY (self), FALSE); return (q != 0 && g_datalist_id_get_data (&proxy->priv->interfaces, q) != NULL); } static void tp_proxy_lose_interface (GQuark unused, gpointer dgproxy_or_self, gpointer self) { if (dgproxy_or_self != self) g_signal_handlers_disconnect_by_func (dgproxy_or_self, G_CALLBACK (tp_proxy_iface_destroyed_cb), self); } static void tp_proxy_lose_interfaces (TpProxy *self) { g_datalist_foreach (&self->priv->interfaces, tp_proxy_lose_interface, self); g_datalist_clear (&self->priv->interfaces); } static void tp_proxy_poll_features (TpProxy *self, const GError *error); /* This signature is chosen to match GSourceFunc */ static gboolean tp_proxy_emit_invalidated (gpointer p) { TpProxy *self = TP_PROXY (p); g_signal_emit (self, signals[SIGNAL_INVALIDATED], 0, self->invalidated->domain, self->invalidated->code, self->invalidated->message); /* make all pending tp_proxy_prepare_async calls fail */ tp_proxy_poll_features (self, NULL); g_assert_cmpuint (g_queue_get_length (self->priv->prepare_requests), ==, 0); /* Don't clear the datalist until after we've emitted the signal, so * the pending call and signal connection friend classes can still get * to the proxies */ tp_proxy_lose_interfaces (self); if (self->dbus_connection != NULL) { dbus_g_connection_unref (self->dbus_connection); self->dbus_connection = NULL; } return FALSE; } /** * tp_proxy_invalidate: * @self: a proxy * @error: an error causing the invalidation * * Mark @self as having been invalidated - no further calls will work, and * if not already invalidated, the #TpProxy::invalidated signal will be emitted * with the given error. * * Since: 0.7.1 */ void tp_proxy_invalidate (TpProxy *self, const GError *error) { g_return_if_fail (self != NULL); g_return_if_fail (error != NULL); if (self->invalidated == NULL) { DEBUG ("%p: %s", self, error->message); self->invalidated = g_error_copy (error); tp_proxy_emit_invalidated (self); } } static void tp_proxy_iface_destroyed_cb (DBusGProxy *dgproxy, TpProxy *self) { /* We can't call any API on the proxy now. Because the proxies are all * for the same bus name, we can assume that all of them are equally * useless now */ tp_proxy_lose_interfaces (self); /* We need to be able to delay emitting the invalidated signal, so that * any queued-up method calls and signal handlers will run first, and so * it doesn't try to reenter libdbus. */ if (self->invalidated == NULL) { DEBUG ("%p", self); self->invalidated = g_error_new_literal (TP_DBUS_ERRORS, TP_DBUS_ERROR_NAME_OWNER_LOST, "Name owner lost (service crashed?)"); g_idle_add_full (G_PRIORITY_HIGH, tp_proxy_emit_invalidated, g_object_ref (self), g_object_unref); } } /** * tp_proxy_add_interface_by_id: (skip) * @self: the TpProxy, which must not have become #TpProxy::invalidated. * @iface: quark representing the interface to be added * * Declare that this proxy supports a given interface. * * To use methods and signals of that interface, either call * tp_proxy_get_interface_by_id() to get the #DBusGProxy, or use the * tp_cli_* wrapper functions (strongly recommended). * * If the interface is the proxy's "main interface", or has already been * added, then do nothing. * * Returns: either %NULL or a borrowed #DBusGProxy corresponding to @iface, * depending on implementation details. To reliably borrow the #DBusGProxy, use * tp_proxy_get_interface_by_id(). (This method should probably have * returned void; sorry.) * * Since: 0.7.1 */ DBusGProxy * tp_proxy_add_interface_by_id (TpProxy *self, GQuark iface) { DBusGProxy *iface_proxy = g_datalist_id_get_data (&self->priv->interfaces, iface); g_return_val_if_fail (tp_dbus_check_valid_interface_name (g_quark_to_string (iface), NULL), NULL); g_return_val_if_fail (tp_proxy_get_invalidated (self) == NULL, NULL); if (iface_proxy == NULL) { /* we don't want to actually create it just yet - dbus-glib will * helpfully wake us up on every signal, if we do. So we set a * dummy value (self), and replace it with the real value in * tp_proxy_get_interface_by_id */ g_datalist_id_set_data_full (&self->priv->interfaces, iface, self, NULL); } return iface_proxy; } /** * tp_proxy_add_interfaces: (skip) * @self: the TpProxy, which must not have become #TpProxy::invalidated. * @interfaces: the names of the interfaces to be added * * Declare that this proxy supports the given interfaces. Equivalent to calling * g_quark_from_string () followed by tp_proxy_add_interface_by_id () for each * of the interface names. * * Since: 0.14.4 */ void tp_proxy_add_interfaces (TpProxy *self, const gchar * const *interfaces) { const gchar * const *iter; if (G_UNLIKELY (interfaces == NULL)) return; for (iter = interfaces; *iter != NULL; iter++) { if (tp_dbus_check_valid_interface_name (*iter, NULL)) { GQuark q = g_quark_from_string (*iter); tp_proxy_add_interface_by_id (self, q); } else { DEBUG ("Ignoring invalid interface on %s: %s", tp_proxy_get_object_path (self), *iter); } } } static GQuark error_mapping_quark (void) { static GQuark q = 0; if (G_UNLIKELY (q == 0)) { q = g_quark_from_static_string ("TpProxyErrorMappingCb_0.7.1"); } return q; } /** * tp_proxy_dbus_error_to_gerror: * @self: a #TpProxy or subclass * @dbus_error: a D-Bus error name, for instance from the callback for * tp_cli_connection_connect_to_connection_error() * @debug_message: a debug message that accompanied the error name, or %NULL * @error: used to return the corresponding #GError * * Convert a D-Bus error name into a GError as if it was returned by a method * on this proxy. This method is useful when D-Bus error names are emitted in * signals, such as Connection.ConnectionError and * Group.MembersChangedDetailed. * * Since: 0.7.24 */ void tp_proxy_dbus_error_to_gerror (gpointer self, const char *dbus_error, const char *debug_message, GError **error) { GType proxy_type = TP_TYPE_PROXY; GType type; g_return_if_fail (TP_IS_PROXY (self)); if (error == NULL) return; g_return_if_fail (*error == NULL); if (!tp_dbus_check_valid_interface_name (dbus_error, error)) { return; } if (debug_message == NULL) debug_message = ""; for (type = G_TYPE_FROM_INSTANCE (self); type != proxy_type; type = g_type_parent (type)) { TpProxyErrorMappingLink *iter; for (iter = g_type_get_qdata (type, error_mapping_quark ()); iter != NULL; iter = iter->next) { size_t prefix_len = strlen (iter->prefix); if (!strncmp (dbus_error, iter->prefix, prefix_len) && dbus_error[prefix_len] == '.') { GEnumValue *code = g_enum_get_value_by_nick (iter->code_enum_class, dbus_error + prefix_len + 1); if (code != NULL) { g_set_error (error, iter->domain, code->value, "%s", debug_message); return; } } } } /* we don't have an error mapping - so let's just paste the * error name and message into TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR */ g_set_error (error, TP_DBUS_ERRORS, TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR, "%s: %s", dbus_error, debug_message); } GError * _tp_proxy_take_and_remap_error (TpProxy *self, GError *error) { if (error == NULL || error->domain != DBUS_GERROR || error->code != DBUS_GERROR_REMOTE_EXCEPTION) { return error; } else { GError *replacement = NULL; const gchar *dbus = dbus_g_error_get_name (error); tp_proxy_dbus_error_to_gerror (self, dbus, error->message, &replacement); g_error_free (error); return replacement; } } static void dup_quark_into_ptr_array (GQuark q, gpointer unused, gpointer user_data) { GPtrArray *strings = user_data; g_ptr_array_add (strings, g_strdup (g_quark_to_string (q))); } static void tp_proxy_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpProxy *self = TP_PROXY (object); switch (property_id) { case PROP_DBUS_DAEMON: if (TP_IS_DBUS_DAEMON (self)) { g_value_set_object (value, self); } else { g_value_set_object (value, self->dbus_daemon); } break; case PROP_DBUS_CONNECTION: g_value_set_boxed (value, self->dbus_connection); break; case PROP_BUS_NAME: g_value_set_string (value, self->bus_name); break; case PROP_OBJECT_PATH: g_value_set_string (value, self->object_path); break; case PROP_INTERFACES: { GPtrArray *strings = g_ptr_array_new (); g_datalist_foreach (&self->priv->interfaces, dup_quark_into_ptr_array, strings); g_ptr_array_add (strings, NULL); g_value_take_boxed (value, g_ptr_array_free (strings, FALSE)); } break; case PROP_FACTORY: g_value_set_object (value, self->priv->factory); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void tp_proxy_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { TpProxy *self = TP_PROXY (object); switch (property_id) { case PROP_DBUS_DAEMON: if (TP_IS_DBUS_DAEMON (self)) { g_assert (g_value_get_object (value) == NULL); } else { TpProxy *daemon_as_proxy = TP_PROXY (g_value_get_object (value)); g_assert (self->dbus_daemon == NULL); if (daemon_as_proxy != NULL) self->dbus_daemon = TP_DBUS_DAEMON (g_object_ref (daemon_as_proxy)); if (daemon_as_proxy != NULL) { g_assert (self->dbus_connection == NULL || self->dbus_connection == daemon_as_proxy->dbus_connection); if (self->dbus_connection == NULL) self->dbus_connection = dbus_g_connection_ref (daemon_as_proxy->dbus_connection); } } break; case PROP_DBUS_CONNECTION: { DBusGConnection *conn = g_value_get_boxed (value); /* if we're given a NULL dbus-connection, but we've got a * DBusGConnection from the dbus-daemon, we want to keep it */ if (conn == NULL) return; if (self->dbus_connection == NULL) self->dbus_connection = g_value_dup_boxed (value); g_assert (self->dbus_connection == g_value_get_boxed (value)); } break; case PROP_BUS_NAME: g_assert (self->bus_name == NULL); self->bus_name = g_value_dup_string (value); break; case PROP_OBJECT_PATH: g_assert (self->object_path == NULL); self->object_path = g_value_dup_string (value); break; case PROP_FACTORY: g_assert (self->priv->factory == NULL); self->priv->factory = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void tp_proxy_init (TpProxy *self) { self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_PROXY, TpProxyPrivate); self->priv->prepare_requests = g_queue_new (); } static GQuark interface_added_cb_quark (void) { static GQuark q = 0; if (G_UNLIKELY (q == 0)) { q = g_quark_from_static_string ("TpProxyInterfaceAddedCb_0.7.1"); } return q; } static FeatureState tp_proxy_get_feature_state (TpProxy *self, GQuark feature) { return GPOINTER_TO_INT (g_datalist_id_get_data (&self->priv->features, feature)); } static void tp_proxy_set_feature_state (TpProxy *self, GQuark feature, FeatureState state) { g_datalist_id_set_data (&self->priv->features, feature, GINT_TO_POINTER (state)); } static void assert_feature_validity (TpProxy *self, const TpProxyFeature *feature) { g_assert (feature != NULL); /* Core features can't have depends, their depends are implicit */ if (feature->core) g_assert (feature->depends_on == NULL || feature->depends_on[0] == 0); /* prepare_before_signalling_connected_async only make sense for * TpConnection subclasses */ if (feature->prepare_before_signalling_connected_async != NULL) g_assert (TP_IS_CONNECTION (self)); } static GObject * tp_proxy_constructor (GType type, guint n_params, GObjectConstructParam *params) { GObjectClass *object_class = (GObjectClass *) tp_proxy_parent_class; TpProxy *self = TP_PROXY (object_class->constructor (type, n_params, params)); TpProxyClass *klass = TP_PROXY_GET_CLASS (self); TpProxyInterfaceAddLink *iter; GType proxy_parent_type = G_TYPE_FROM_CLASS (tp_proxy_parent_class); GType ancestor_type; _tp_register_dbus_glib_marshallers (); for (ancestor_type = type; ancestor_type != proxy_parent_type && ancestor_type != 0; ancestor_type = g_type_parent (ancestor_type)) { TpProxyClass *ancestor = g_type_class_peek (ancestor_type); const TpProxyFeature *features; guint i; GArray *core_features; for (iter = g_type_get_qdata (ancestor_type, interface_added_cb_quark ()); iter != NULL; iter = iter->next) g_signal_connect (self, "interface-added", G_CALLBACK (iter->callback), NULL); if (ancestor == NULL || ancestor->list_features == NULL) continue; features = ancestor->list_features (ancestor); if (features == NULL) continue; core_features = g_array_new (TRUE, FALSE, sizeof (GQuark)); for (i = 0; features[i].name != 0; i++) { assert_feature_validity (self, &features[i]); tp_proxy_set_feature_state (self, features[i].name, FEATURE_STATE_UNWANTED); if (features[i].core) { g_array_append_val (core_features, features[i].name); } } if (core_features->len > 0) { TpProxyPrepareRequest *req; req = tp_proxy_prepare_request_new (NULL, (const GQuark *) core_features->data); req->core = TRUE; g_queue_push_head (self->priv->prepare_requests, req); DEBUG ("%p: request %p represents core features on %s", self, req, g_type_name (ancestor_type)); } g_array_unref (core_features); } g_return_val_if_fail (self->dbus_connection != NULL, NULL); g_return_val_if_fail (self->object_path != NULL, NULL); g_return_val_if_fail (self->bus_name != NULL, NULL); g_return_val_if_fail (tp_dbus_check_valid_object_path (self->object_path, NULL), NULL); g_return_val_if_fail (tp_dbus_check_valid_bus_name (self->bus_name, TP_DBUS_NAME_TYPE_ANY, NULL), NULL); tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_INTROSPECTABLE); tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_PEER); tp_proxy_add_interface_by_id (self, TP_IFACE_QUARK_DBUS_PROPERTIES); if (klass->interface != 0) { tp_proxy_add_interface_by_id (self, klass->interface); } /* Some interfaces are stateful, so we only allow binding to a unique * name, like in dbus_g_proxy_new_for_name_owner() */ if (klass->must_have_unique_name) { g_return_val_if_fail (self->bus_name[0] == ':', NULL); } return (GObject *) self; } static GQuark const no_quarks[] = { 0 }; static void tp_proxy_dispose (GObject *object) { TpProxy *self = TP_PROXY (object); GError e = { TP_DBUS_ERRORS, TP_DBUS_ERROR_PROXY_UNREFERENCED, "Proxy unreferenced" }; if (self->priv->dispose_has_run) return; self->priv->dispose_has_run = TRUE; DEBUG ("%p", self); tp_proxy_invalidate (self, &e); tp_clear_object (&self->dbus_daemon); tp_clear_object (&self->priv->factory); G_OBJECT_CLASS (tp_proxy_parent_class)->dispose (object); } static void tp_proxy_finalize (GObject *object) { TpProxy *self = TP_PROXY (object); DEBUG ("%p", self); if (self->priv->features != NULL) g_datalist_clear (&self->priv->features); g_assert (self->invalidated != NULL); g_error_free (self->invalidated); /* invalidation ensures that these have gone away */ g_assert_cmpuint (g_queue_get_length (self->priv->prepare_requests), ==, 0); tp_clear_pointer (&self->priv->prepare_requests, g_queue_free); g_free (self->bus_name); g_free (self->object_path); G_OBJECT_CLASS (tp_proxy_parent_class)->finalize (object); } /** * tp_proxy_or_subclass_hook_on_interface_add: * @proxy_or_subclass: The #GType of #TpProxy or a subclass * @callback: A signal handler for #TpProxy::interface-added * * Arrange for @callback to be connected to #TpProxy::interface-added * during the #TpProxy constructor. This is done sufficiently early that * it will see the signal for the default interface (@interface member of * #TpProxyClass), if any, being added. The intended use is for the callback * to call dbus_g_proxy_add_signal() on the new #DBusGProxy. * * Since 0.7.6, to ensure correct overriding of interfaces that might be * added to telepathy-glib, before calling this function you should * call tp_proxy_init_known_interfaces, tp_connection_init_known_interfaces, * tp_channel_init_known_interfaces etc. as appropriate for the subclass. * * Since: 0.7.1 */ void tp_proxy_or_subclass_hook_on_interface_add (GType proxy_or_subclass, TpProxyInterfaceAddedCb callback) { GQuark q = interface_added_cb_quark (); TpProxyInterfaceAddLink *old_link = g_type_get_qdata (proxy_or_subclass, q); TpProxyInterfaceAddLink *new_link; g_return_if_fail (g_type_is_a (proxy_or_subclass, TP_TYPE_PROXY)); g_return_if_fail (callback != NULL); /* never freed, suppressed in telepathy-glib.supp */ new_link = g_slice_new0 (TpProxyInterfaceAddLink); new_link->callback = callback; new_link->next = old_link; /* may be NULL */ g_type_set_qdata (proxy_or_subclass, q, new_link); } /** * tp_proxy_subclass_add_error_mapping: * @proxy_subclass: The #GType of a subclass of #TpProxy (which must not be * #TpProxy itself) * @static_prefix: A prefix for D-Bus error names, not including the trailing * dot (which must remain valid forever, and should usually be in static * storage) * @domain: A quark representing the corresponding #GError domain * @code_enum_type: The type of a subclass of #GEnumClass * * Register a mapping from D-Bus errors received from the given proxy * subclass to #GError instances. * * When a D-Bus error is received, the #TpProxy code checks for error * mappings registered for the class of the proxy receiving the error, * then for all of its parent classes. * * If there is an error mapping for which the D-Bus error name * starts with the mapping's @static_prefix, the proxy will check the * corresponding @code_enum_type for a value whose @value_nick is * the rest of the D-Bus error name (with the leading dot removed). If there * isn't such a value, it will continue to try other error mappings. * * If a suitable error mapping and code are found, the #GError that is raised * will have its error domain set to the @domain from the error mapping, * and its error code taken from the enum represented by the @code_enum_type. * * If no suitable error mapping or code is found, the #GError will have * error domain %TP_DBUS_ERRORS and error code * %TP_DBUS_ERROR_UNKNOWN_REMOTE_ERROR. * * Since: 0.7.1 */ void tp_proxy_subclass_add_error_mapping (GType proxy_subclass, const gchar *static_prefix, GQuark domain, GType code_enum_type) { GQuark q = error_mapping_quark (); TpProxyErrorMappingLink *old_link = g_type_get_qdata (proxy_subclass, q); TpProxyErrorMappingLink *new_link; GType tp_type_proxy = TP_TYPE_PROXY; g_return_if_fail (proxy_subclass != tp_type_proxy); g_return_if_fail (g_type_is_a (proxy_subclass, tp_type_proxy)); g_return_if_fail (static_prefix != NULL); g_return_if_fail (domain != 0); g_return_if_fail (code_enum_type != G_TYPE_INVALID); new_link = g_slice_new0 (TpProxyErrorMappingLink); new_link->prefix = static_prefix; new_link->domain = domain; /* We never unref the enum type - intentional one-per-process leak. * See "tp_proxy_subclass_add_error_mapping refs the enum" in our valgrind * suppressions file */ new_link->code_enum_class = g_type_class_ref (code_enum_type); new_link->next = old_link; /* may be NULL */ g_type_set_qdata (proxy_subclass, q, new_link); } static void tp_proxy_class_init (TpProxyClass *klass) { GParamSpec *param_spec; GObjectClass *object_class = G_OBJECT_CLASS (klass); tp_proxy_init_known_interfaces (); g_type_class_add_private (klass, sizeof (TpProxyPrivate)); object_class->constructor = tp_proxy_constructor; object_class->get_property = tp_proxy_get_property; object_class->set_property = tp_proxy_set_property; object_class->dispose = tp_proxy_dispose; object_class->finalize = tp_proxy_finalize; /** * TpProxy:dbus-daemon: * * The D-Bus daemon for this object (this object itself, if it is a * TpDBusDaemon). Read-only except during construction. */ param_spec = g_param_spec_object ("dbus-daemon", "D-Bus daemon", "The D-Bus daemon used by this object, or this object itself if it's " "a TpDBusDaemon", TP_TYPE_DBUS_DAEMON, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_DBUS_DAEMON, param_spec); /** * TpProxy:dbus-connection: (skip) * * The D-Bus connection for this object. Read-only except during * construction. */ param_spec = g_param_spec_boxed ("dbus-connection", "D-Bus connection", "The D-Bus connection used by this object", DBUS_TYPE_G_CONNECTION, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_DBUS_CONNECTION, param_spec); /** * TpProxy:bus-name: * * The D-Bus bus name for this object. Read-only except during construction. */ param_spec = g_param_spec_string ("bus-name", "D-Bus bus name", "The D-Bus bus name for this object", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_BUS_NAME, param_spec); /** * TpProxy:object-path: * * The D-Bus object path for this object. Read-only except during * construction. */ param_spec = g_param_spec_string ("object-path", "D-Bus object path", "The D-Bus object path for this object", NULL, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_OBJECT_PATH, param_spec); /** * TpProxy:interfaces: * * Known D-Bus interface names for this object. */ param_spec = g_param_spec_boxed ("interfaces", "D-Bus interfaces", "Known D-Bus interface names for this object", G_TYPE_STRV, G_PARAM_READABLE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_INTERFACES, param_spec); /** * TpProxy:factory: * * The #TpSimpleClientFactory used to create this proxy, * or %NULL if this proxy was not created through a factory. */ param_spec = g_param_spec_object ("factory", "Simple Client Factory", "The TpSimpleClientFactory used to create this proxy", TP_TYPE_SIMPLE_CLIENT_FACTORY, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS); g_object_class_install_property (object_class, PROP_FACTORY, param_spec); /** * TpProxy::interface-added: (skip) * @self: the proxy object * @id: the GQuark representing the interface * @proxy: the dbus-glib proxy representing the interface * * Emitted when this proxy has gained an interface. It is not guaranteed * to be emitted immediately, but will be emitted before the interface is * first used (at the latest: before it's returned from * tp_proxy_get_interface_by_id(), any signal is connected, or any * method is called). * * The intended use is to call dbus_g_proxy_add_signals(). This signal * should only be used by TpProy implementations */ signals[SIGNAL_INTERFACE_ADDED] = g_signal_new ("interface-added", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, NULL, G_TYPE_NONE, 2, G_TYPE_UINT, DBUS_TYPE_G_PROXY); /** * TpProxy::invalidated: * @self: the proxy object * @domain: domain of a GError indicating why this proxy was invalidated * @code: error code of a GError indicating why this proxy was invalidated * @message: a message associated with the error * * Emitted when this proxy has been become invalid for * whatever reason. Any more specific signal should be emitted first. * * An invalidated proxy is one which can make no more method calls and will * emit no more D-Bus signals. This is typically because the D-Bus object * represented by the proxy ceased to exist, or there was some error * obtaining the initial state. * * Any pending or future method calls made on this proxy will fail gracefully * with the same error as returned by tp_proxy_get_invalidated(). */ signals[SIGNAL_INVALIDATED] = g_signal_new ("invalidated", G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED, 0, NULL, NULL, NULL, G_TYPE_NONE, 3, G_TYPE_UINT, G_TYPE_INT, G_TYPE_STRING); } /** * tp_proxy_get_factory: * @self: a #TpProxy or subclass * * * * Returns: (transfer none): the same value as #TpProxy:factory property * * Since: 0.15.5 */ TpSimpleClientFactory * tp_proxy_get_factory (gpointer self) { TpProxy *proxy = self; g_return_val_if_fail (TP_IS_PROXY (self), NULL); return proxy->priv->factory; } void _tp_proxy_ensure_factory (gpointer proxy, TpSimpleClientFactory *factory) { TpProxy *self = TP_PROXY (proxy); if (self->priv->factory != NULL) return; if (factory != NULL) { self->priv->factory = g_object_ref (factory); } else { self->priv->factory = (TpSimpleClientFactory *) tp_automatic_client_factory_new (self->dbus_daemon); } _tp_simple_client_factory_insert_proxy (self->priv->factory, self); } /** * tp_proxy_get_dbus_daemon: * @self: a #TpProxy or subclass * * * * Returns: (transfer none): a borrowed reference to the #TpDBusDaemon for * this object, if any; always %NULL if this object is itself a * #TpDBusDaemon. The caller must reference the returned object with * g_object_ref() if it will be kept. * * Since: 0.7.17 */ TpDBusDaemon * tp_proxy_get_dbus_daemon (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->dbus_daemon; } /** * tp_proxy_get_dbus_connection: (skip) * @self: a #TpProxy or subclass * * * * Returns: a borrowed reference to the D-Bus connection used by this object. * The caller must reference the returned pointer with * dbus_g_connection_ref() if it will be kept. * * Since: 0.7.17 */ DBusGConnection * tp_proxy_get_dbus_connection (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->dbus_connection; } /** * tp_proxy_get_bus_name: * @self: a #TpProxy or subclass * * * * Returns: the bus name of the application exporting the object. The caller * must copy the string with g_strdup() if it will be kept. * * Since: 0.7.17 */ const gchar * tp_proxy_get_bus_name (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->bus_name; } /** * tp_proxy_get_object_path: * @self: a #TpProxy or subclass * * * * Returns: the object path of the remote object. The caller must copy the * string with g_strdup() if it will be kept. * * Since: 0.7.17 */ const gchar * tp_proxy_get_object_path (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->object_path; } /** * tp_proxy_get_invalidated: * @self: a #TpProxy or subclass * * * * Returns: the reason this proxy was invalidated, or %NULL if has not been * invalidated. The caller must copy the error, for instance with * g_error_copy(), if it will be kept. * * Since: 0.7.17 */ const GError * tp_proxy_get_invalidated (gpointer self) { TpProxy *proxy = TP_PROXY (self); return proxy->invalidated; } /** * tp_proxy_dbus_g_proxy_claim_for_signal_adding: * @proxy: a #DBusGProxy * * Attempt to "claim" a #DBusGProxy for addition of signal signatures. * If this function has not been called on @proxy before, %TRUE is * returned, and the caller may safely call dbus_g_proxy_add_signal() * on @proxy. If this function has already been caled, %FALSE is * returned, and the caller may not safely call dbus_g_proxy_add_signal(). * * This is intended for use by auto-generated signal-adding functions, * to allow interfaces provided as local extensions to override those in * telepathy-glib without causing assertion failures. * * Returns: %TRUE if it is safe to call dbus_g_proxy_add_signal() * Since: 0.7.6 */ gboolean tp_proxy_dbus_g_proxy_claim_for_signal_adding (DBusGProxy *proxy) { static GQuark q = 0; g_return_val_if_fail (proxy != NULL, FALSE); if (G_UNLIKELY (q == 0)) { q = g_quark_from_static_string ( "tp_proxy_dbus_g_proxy_claim_for_signal_adding@0.7.6"); } if (g_object_get_qdata ((GObject *) proxy, q) != NULL) { /* Someone else has already added signal signatures for this interface. * We can't do it again or it'll cause an assertion */ return FALSE; } /* the proxy is just used as qdata here because it's a convenient * non-NULL pointer */ g_object_set_qdata ((GObject *) proxy, q, proxy); return TRUE; } static gpointer tp_proxy_once (gpointer data G_GNUC_UNUSED) { GType type = TP_TYPE_PROXY; tp_proxy_or_subclass_hook_on_interface_add (type, tp_cli_generic_add_signals); return NULL; } /** * tp_proxy_init_known_interfaces: * * Ensure that the known interfaces for TpProxy have been set up. * This is done automatically when necessary, but for correct * overriding of library interfaces by local extensions, you should * call this function before calling * tp_proxy_or_subclass_hook_on_interface_add(). * * Functions like tp_connection_init_known_interfaces and * tp_channel_init_known_interfaces do this automatically. * * Since: 0.7.6 */ void tp_proxy_init_known_interfaces (void) { static GOnce once = G_ONCE_INIT; g_once (&once, tp_proxy_once, NULL); } static const TpProxyFeature * tp_proxy_subclass_get_feature (GType type, GQuark feature) { GType proxy_type = TP_TYPE_PROXY; g_return_val_if_fail (g_type_is_a (type, proxy_type), NULL); /* we stop at proxy_type since we know that TpProxy has no features */ for ( ; type != proxy_type; type = g_type_parent (type)) { guint i; TpProxyClass *cls = g_type_class_ref (type); const TpProxyFeature *features; if (cls->list_features == NULL) goto cont; features = cls->list_features (cls); if (features == NULL) goto cont; for (i = 0; features[i].name != 0; i++) { if (features[i].name == feature) { g_type_class_unref (cls); return features + i; } } cont: g_type_class_unref (cls); } return FALSE; } /** * tp_proxy_is_prepared: * @self: an instance of a #TpProxy subclass * @feature: a feature that is supported by @self's class * * Return %TRUE if @feature has been prepared successfully, or %FALSE if * @feature has not been requested, has not been prepared yet, or is not * available on this object at all. * * (For instance, if @feature is %TP_CHANNEL_FEATURE_CHAT_STATES and @self * is a #TpChannel in a protocol that doesn't actually implement chat states, * or is not a #TpChannel at all, then this method will return %FALSE.) * * To prepare features, call tp_proxy_prepare_async(). * * Returns: %TRUE if @feature has been prepared successfully * * Since: 0.11.3 */ gboolean tp_proxy_is_prepared (gpointer self, GQuark feature) { FeatureState state; g_return_val_if_fail (TP_IS_PROXY (self), FALSE); if (tp_proxy_get_invalidated (self) != NULL) return FALSE; state = tp_proxy_get_feature_state (self, feature); return (state == FEATURE_STATE_READY); } /* * _tp_proxy_is_preparing: * @self: an instance of a #TpProxy subclass * @feature: a feature that is supported by @self's class * * Return %TRUE if @feature has been requested, but has not been prepared * successfully or unsuccessfully yet. * * It is an error to use a @feature not specifically supported by @self - for * instance, it is an error to use %TP_CHANNEL_FEATURE_CHAT_STATES on any * #TpProxy that is not also a #TpChannel. * * Subclasses of #TpProxy should use this method to check whether to take * action for a particular feature. For instance, #TpChannel could call this * method for %TP_CHANNEL_CHAT_STATES when it discovers that the ChatStates * interface is supported, to decide whether to fetch the state of that * interface. * * Returns: %TRUE if @feature has been requested, but preparing it has neither * succeeded nor failed yet */ gboolean _tp_proxy_is_preparing (gpointer self, GQuark feature) { FeatureState state; g_return_val_if_fail (TP_IS_PROXY (self), FALSE); if (tp_proxy_get_invalidated (self) != NULL) return FALSE; state = tp_proxy_get_feature_state (self, feature); g_return_val_if_fail (state != FEATURE_STATE_INVALID, FALSE); return (state == FEATURE_STATE_WANTED || state == FEATURE_STATE_TRYING); } static gboolean check_feature_interfaces (TpProxy *self, GQuark name) { const TpProxyFeature *feature = tp_proxy_subclass_get_feature ( G_OBJECT_TYPE (self), name); guint i; if (feature->interfaces_needed == NULL) return TRUE; for (i = 0; feature->interfaces_needed[i] != 0; i++) { if (!tp_proxy_has_interface_by_id (self, feature->interfaces_needed[i])) { DEBUG ("Proxy doesn't implement %s, can't prepare feature %s", g_quark_to_string (feature->interfaces_needed[i]), g_quark_to_string (name)); return FALSE; } } return TRUE; } /* Returns %TRUE if all the deps of @name are ready * @can_retry: if %TRUE dependencies which have failed but have * TpProxyFeature.can_retry won't be considered as having failed so we'll * still have a change to retry preparing those. * @failed: (out): %TRUE if one of @name's dep can't be prepared and so * @name can't be either */ static gboolean check_depends_ready (TpProxy *self, GQuark name, gboolean can_retry, gboolean *failed) { const TpProxyFeature *feature = tp_proxy_subclass_get_feature ( G_OBJECT_TYPE (self), name); guint i; gboolean ready = TRUE; g_assert (failed != NULL); *failed = FALSE; if (feature->depends_on == NULL) return TRUE; for (i = 0; feature->depends_on[i] != 0; i++) { GQuark dep = feature->depends_on[i]; const TpProxyFeature *dep_feature = tp_proxy_subclass_get_feature ( G_OBJECT_TYPE (self), dep); FeatureState dep_state; dep_state = tp_proxy_get_feature_state (self, dep); switch (dep_state) { case FEATURE_STATE_INVALID: DEBUG ("Can't prepare %s, because %s (a dependency) is " "invalid", g_quark_to_string (name), g_quark_to_string (dep)); *failed = TRUE; return FALSE; case FEATURE_STATE_FAILED: case FEATURE_STATE_MISSING_IFACE: if (!can_retry || !dep_feature->can_retry) { DEBUG ("Can't prepare %s, because %s (a dependency) is " "failed to prepare", g_quark_to_string (name), g_quark_to_string (dep)); *failed = TRUE; return FALSE; } DEBUG ("retry preparing dep: %s", g_quark_to_string (dep)); tp_proxy_set_feature_state (self, dep, FEATURE_STATE_WANTED); ready = FALSE; break; case FEATURE_STATE_UNWANTED: case FEATURE_STATE_WANTED: case FEATURE_STATE_TRYING: ready = FALSE; break; case FEATURE_STATE_READY: break; } } return ready; } static void depends_prepare_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TpProxy *self = TP_PROXY (source); tp_proxy_poll_features (self, NULL); } static void prepare_depends (TpProxy *self, GQuark name) { const TpProxyFeature *feature; feature = tp_proxy_subclass_get_feature (G_OBJECT_TYPE (self), name); g_assert (feature->depends_on != NULL); tp_proxy_prepare_async (self, feature->depends_on, depends_prepare_cb, NULL); } /** * tp_proxy_prepare_async: * @self: an instance of a #TpProxy subclass * @features: (transfer none) (array zero-terminated=1) (allow-none): an array * of desired features, ending with 0; %NULL is equivalent to an array * containing only 0 * @callback: if not %NULL, called exactly once, when the features have all * been prepared or failed to prepare, or after the proxy is invalidated * @user_data: user data for @callback * * #TpProxy itself does not support any features, but subclasses like * #TpChannel can support features, which can either be core functionality like * %TP_CHANNEL_FEATURE_CORE, or extended functionality like * %TP_CHANNEL_FEATURE_CHAT_STATES. * * Proxy instances start with no features prepared. When features are * requested via tp_proxy_prepare_async(), the proxy starts to do the * necessary setup to use those features. * * tp_proxy_prepare_async() always waits for core functionality of the proxy's * class to be prepared, even if it is not specifically requested: for * instance, because %TP_CHANNEL_FEATURE_CORE is core functionality of a * #TpChannel, * * |[ * TpChannel *channel = ...; * * tp_proxy_prepare_async (channel, NULL, callback, user_data); * ]| * * is equivalent to * * |[ * TpChannel *channel = ...; * GQuark features[] = { TP_CHANNEL_FEATURE_CORE, 0 }; * * tp_proxy_prepare_async (channel, features, callback, user_data); * ]| * * If a feature represents core functionality (like %TP_CHANNEL_FEATURE_CORE), * failure to prepare it will result in tp_proxy_prepare_async() finishing * unsuccessfully: if failure to prepare the feature indicates that the proxy * is no longer useful, it will also emit #TpProxy::invalidated. * * If a feature represents non-essential functionality * (like %TP_CHANNEL_FEATURE_CHAT_STATES), or is not supported by the object * at all, then failure to prepare it is not fatal: * tp_proxy_prepare_async() will complete successfully, but * tp_proxy_is_prepared() will still return %FALSE for the feature, and * accessor methods for the feature will typically return a dummy value. * * Some #TpProxy subclasses automatically start to prepare their core * features when instantiated, and features will sometimes become prepared as * a side-effect of other actions, but to ensure that a feature is present you * must generally call tp_proxy_prepare_async() and wait for the result. * * Since: 0.11.3 */ void tp_proxy_prepare_async (gpointer self, const GQuark *features, GAsyncReadyCallback callback, gpointer user_data) { TpProxy *proxy = self; GSimpleAsyncResult *result = NULL; guint i; g_return_if_fail (TP_IS_PROXY (self)); if (features == NULL) features = no_quarks; for (i = 0; features[i] != 0; i++) { FeatureState state = tp_proxy_get_feature_state (self, features[i]); const TpProxyFeature *feature = tp_proxy_subclass_get_feature ( G_OBJECT_TYPE (self), features[i]); /* We just skip unknown features, which have state FEATURE_STATE_INVALID * (this doesn't seem ideal, but is * consistent with TpAccountManager's existing behaviour) */ if (state == FEATURE_STATE_INVALID) { continue; } else if (state == FEATURE_STATE_UNWANTED || (state == FEATURE_STATE_FAILED && feature->can_retry)) { gboolean failed; /* Check deps. We only offer there the chance to retry a previously * failed dependency. Doing it in tp_proxy_poll_features() could * result in an infinite loop if we'd depends on 2 features which * are constantly failing. */ if (!check_depends_ready (self, features[i], TRUE, &failed)) { if (failed) { /* We can't prepare the feature because of its deps */ tp_proxy_set_feature_state (self, features[i], FEATURE_STATE_FAILED); continue; } prepare_depends (self, features[i]); } tp_proxy_set_feature_state (self, features[i], FEATURE_STATE_WANTED); } } if (callback != NULL) result = g_simple_async_result_new (self, callback, user_data, tp_proxy_prepare_async); if (proxy->invalidated != NULL) { if (result != NULL) { g_simple_async_result_set_from_error (result, proxy->invalidated); g_simple_async_result_complete_in_idle (result); } goto finally; } g_queue_push_tail (proxy->priv->prepare_requests, tp_proxy_prepare_request_new (result, features)); tp_proxy_poll_features (proxy, NULL); finally: if (result != NULL) g_object_unref (result); } /** * tp_proxy_prepare_finish: * @self: an instance of a #TpProxy subclass * @result: the result passed to the callback of tp_proxy_prepare_async() * @error: used to return an error if %FALSE is returned * * Check for error in a call to tp_proxy_prepare_async(). An error here * generally indicates that either the asynchronous call was cancelled, * or @self has emitted #TpProxy::invalidated. * * Returns: %FALSE (setting @error) if tp_proxy_prepare_async() failed * or was cancelled * * Since: 0.11.3 */ gboolean tp_proxy_prepare_finish (gpointer self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, tp_proxy_prepare_async); } static gboolean prepare_finish (TpProxy *self, GAsyncResult *result, gpointer source, GError **error) { _tp_implement_finish_void (self, source); } static void feature_prepared_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TpProxy *self = TP_PROXY (source); TpProxyFeature *feature = user_data; GError *error = NULL; gboolean prepared = TRUE; if (!prepare_finish (self, result, feature->prepare_async, &error)) { DEBUG ("Failed to prepare %s: %s", g_quark_to_string (feature->name), error->message); prepared = FALSE; g_error_free (error); } _tp_proxy_set_feature_prepared (self, feature->name, prepared); } static void prepare_feature (TpProxy *self, const TpProxyFeature *feature) { /* If no function is set, then subclass is supposed to call * _tp_proxy_set_feature_prepared() itself. This is used by features prepared * from constructed. */ if (feature->prepare_async == NULL) return; feature->prepare_async (self, feature, feature_prepared_cb, (gpointer) feature); } static gboolean core_prepared (TpProxy *self) { /* All the core features have been prepared if the head of the * prepare_requests queue is NOT a core feature */ TpProxyPrepareRequest *req = g_queue_peek_head (self->priv->prepare_requests); if (req == NULL) return TRUE; return !req->core; } /* Returns %TRUE if all the features requested in @req have complete their * preparation */ static gboolean request_is_complete (TpProxy *self, TpProxyPrepareRequest *req) { guint i; gboolean complete = TRUE; for (i = 0; i < req->features->len; i++) { GQuark feature = g_array_index (req->features, GQuark, i); FeatureState state = tp_proxy_get_feature_state (self, feature); const TpProxyFeature *feat_struct = tp_proxy_subclass_get_feature ( G_OBJECT_TYPE (self), feature); switch (state) { case FEATURE_STATE_UNWANTED: /* this can only happen in the special pseudo-request for the * core features, which blocks everything */ g_assert (req->core); complete = FALSE; /* fall through to treat it as WANTED */ case FEATURE_STATE_WANTED: if (core_prepared (self) || req->core) { gboolean failed; /* Check if we have the required interfaces. We can't do that * in tp_proxy_prepare_async() as CORE have to be prepared */ if (!check_feature_interfaces (self, feature)) { if (TP_IS_CONNECTION (self) && tp_connection_get_status ((TpConnection *) self, NULL) != TP_CONNECTION_STATUS_CONNECTED) { /* Give a chance to retry preparing the feature once * the Connection is connected as it may still gain * the interface. */ tp_proxy_set_feature_state (self, feature, FEATURE_STATE_MISSING_IFACE); } else { tp_proxy_set_feature_state (self, feature, FEATURE_STATE_FAILED); } continue; } if (check_depends_ready (self, feature, FALSE, &failed)) { /* We can prepare it now */ DEBUG ("%p: calling callback for %s", self, g_quark_to_string (feature)); tp_proxy_set_feature_state (self, feature, FEATURE_STATE_TRYING); prepare_feature (self, feat_struct); complete = FALSE; } else if (failed) { tp_proxy_set_feature_state (self, feature, FEATURE_STATE_FAILED); } else { /* We have to wait until the deps finish their * preparation. */ complete = FALSE; } } break; case FEATURE_STATE_TRYING: complete = FALSE; break; case FEATURE_STATE_INVALID: case FEATURE_STATE_FAILED: case FEATURE_STATE_MISSING_IFACE: case FEATURE_STATE_READY: /* nothing more to do */ break; } } return complete; } static void finish_all_requests (TpProxy *self, const GError *error) { GList *iter; GQueue *tmp = g_queue_copy (self->priv->prepare_requests); g_queue_clear (self->priv->prepare_requests); for (iter = tmp->head; iter != NULL; iter = g_list_next (iter)) { tp_proxy_prepare_request_finish (iter->data, error); } g_queue_free (tmp); } /* * tp_proxy_poll_features: * @self: a proxy * @error: if not %NULL, fail all feature requests with this error * * For each feature in state WANTED, if its dependencies have been satisfied, * call the callback and advance it to state TRYING. * * For each feature request, see if it's finished yet. * * Called every time the set of prepared/failed features changes, * when a temporary error causes introspection to fail, and when * #TpProxy.invalidated changes. * * If @error is %NULL, #TpProxy.invalidated is also checked. */ static void tp_proxy_poll_features (TpProxy *self, const GError *error) { const gchar *error_source = "temporarily failed"; GList *iter; GList *next; if (g_queue_get_length (self->priv->prepare_requests) == 0) return; g_object_ref (self); for (iter = self->priv->prepare_requests->head; iter != NULL; iter = next) { TpProxyPrepareRequest *req = iter->data; TpProxyPrepareRequest *head = g_queue_peek_head ( self->priv->prepare_requests); if (error == NULL) { error_source = "invalidated"; error = self->invalidated; } if (error != NULL) { DEBUG ("%p: %s, ending all requests", self, error_source); finish_all_requests (self, error); break; } next = iter->next; /* Core features have to be prepared first, in superclass-to-subclass * order. The next core feature to be prepared, if any, is always at the * head of prepare_requests. */ if (!core_prepared (self) && req != head) { DEBUG ("%p: core features not ready yet, nothing prepared", self); continue; } if (request_is_complete (self, req)) { DEBUG ("%p: request %p prepared", self, req); g_queue_delete_link (self->priv->prepare_requests, iter); tp_proxy_prepare_request_finish (req, NULL); } } g_object_unref (self); } /* * _tp_proxy_set_feature_prepared: * @self: a proxy * @feature: a feature made available by @self's class * @succeeded: %TRUE if the feature was prepared successfully * * Record that @self has attempted to prepare @feature. No further * attempts will be made to prepare it. If @succeeded is %TRUE, * tp_proxy_is_prepared() will return %TRUE for @self and @feature. * Whether @succeeded is %TRUE or %FALSE, any calls to * tp_proxy_prepare_async() that were only waiting for @feature will * finish successfully. * * If @feature represents core functionality of the class that should * always have worked (such as the GetAll method call for a #TpAccount's * properties), the subclass should instead call either * _tp_proxy_set_features_failed() (if it might still be possible to use @self * later, as for a #TpConnectionManager) or tp_proxy_invalidate() (if not) * instead; either of these will cause all calls to tp_proxy_prepare_async() * to finish with an error. */ void _tp_proxy_set_feature_prepared (TpProxy *self, GQuark feature, gboolean succeeded) { g_return_if_fail (TP_IS_PROXY (self)); g_return_if_fail (tp_proxy_get_feature_state (self, feature) != FEATURE_STATE_INVALID); tp_proxy_set_feature_state (self, feature, succeeded ? FEATURE_STATE_READY : FEATURE_STATE_FAILED); tp_proxy_poll_features (self, NULL); } /* * _tp_proxy_set_features_failed: * @self: a proxy * @error: an error * * Record that @self has been unable to prepare any features, but is still * potentially usable. Any pending calls to tp_proxy_prepare_async() will * finish unsuccessfully with @error, but @self will *not* be invalidated. */ void _tp_proxy_set_features_failed (TpProxy *self, const GError *error) { g_return_if_fail (TP_IS_PROXY (self)); g_return_if_fail (error != NULL); tp_proxy_poll_features (self, error); } static void check_announce_connected (TpProxy *self, gboolean in_idle) { if (self->priv->pending_will_announce_calls != 0) return; if (in_idle) { g_simple_async_result_complete_in_idle ( self->priv->will_announce_connected_result); } else { g_simple_async_result_complete ( self->priv->will_announce_connected_result); } tp_clear_object (&self->priv->will_announce_connected_result); } static void prepare_before_signalling_connected_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TpProxy *self = TP_PROXY (user_data); /* We don't care if the call succeeded or not as it was already prepared */ self->priv->pending_will_announce_calls--; check_announce_connected (self, FALSE); } static void foreach_feature (GQuark name, gpointer data, gpointer user_data) { FeatureState state = GPOINTER_TO_INT (data); TpProxy *self = user_data; if (state == FEATURE_STATE_MISSING_IFACE) { GQuark features[] = { 0, 0}; tp_proxy_set_feature_state (self, name, FEATURE_STATE_UNWANTED); self->priv->pending_will_announce_calls++; features[0] = name; tp_proxy_prepare_async (self, features, prepare_before_signalling_connected_cb, self); } else if (state == FEATURE_STATE_READY) { const TpProxyFeature *feature; feature = tp_proxy_subclass_get_feature (G_OBJECT_TYPE (self), name); if (feature->prepare_before_signalling_connected_async == NULL) return; self->priv->pending_will_announce_calls++; feature->prepare_before_signalling_connected_async (self, feature, prepare_before_signalling_connected_cb, self); } } /* * _tp_proxy_will_announce_connected_async: * * Called by connection.c when the connection became connected and we're about * to announce it. But before we have to wait for all the prepared features to * process their prepare_before_signalling_connected_async, if any. */ void _tp_proxy_will_announce_connected_async (TpProxy *self, GAsyncReadyCallback callback, gpointer user_data) { g_assert (TP_IS_CONNECTION (self)); g_assert (self->priv->will_announce_connected_result == NULL); self->priv->will_announce_connected_result = g_simple_async_result_new ( (GObject *) self, callback, user_data, _tp_proxy_will_announce_connected_async); g_datalist_foreach (&self->priv->features, foreach_feature, self); check_announce_connected (self, TRUE); } gboolean _tp_proxy_will_announce_connected_finish (TpProxy *self, GAsyncResult *result, GError **error) { _tp_implement_finish_void (self, _tp_proxy_will_announce_connected_async) }