/* * A factory for TpContacts and plain subclasses of TpProxy * * Copyright © 2011 Collabora Ltd. * * 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 */ /** * SECTION:client-factory * @title: TpClientFactory * @short_description: a factory for #TpContacts and plain subclasses * of #TpProxy * @see_also: #TpAutomaticClientFactory * * This factory constructs various #TpProxy subclasses as well as #TpContact, * which guarantees that at most one instance of those objects will exist for a * given remote object or contact. It also stores the desired features for * contacts and each type of proxy. * * Note that the factory will not prepare the desired features: it is the * caller's responsibility to do so. By default, only core features are * requested. * * Currently supported classes are #TpAccount, #TpConnection, * #TpChannel and #TpContact. Those objects should always be acquired through a * factory or a "larger" object (e.g. getting the #TpConnection from * a #TpAccount), rather than being constructed directly. * * One can subclass #TpClientFactory and override some of its virtual * methods to construct more specialized objects. See #TpAutomaticClientFactory * for a subclass which automatically constructs subclasses of #TpChannel for * common channel types. * * An application using its own factory subclass would look like this: * |[ * int main(int argc, char *argv[]) * { * TpClientFactory *factory; * TpAccountManager *manager; * * factory = my_factory_new (); * manager = tp_account_manager_new_with_factory (factory); * tp_account_manager_set_default (manager); * * ... * tp_proxy_prepare_async (manager, am_features, callback, user_data); * ... * } * ]| * * The call to tp_account_manager_set_default() near the beginning of main() * will ensure that any libraries or plugins which also use Telepathy (and call * tp_account_manager_dup()) will share your #TpAccountManager. * * Since: 0.99.1 */ /** * TpClientFactory: * * Data structure representing a #TpClientFactory * * Since: 0.99.1 */ /** * TpClientFactoryClass: * @parent_class: the parent * @create_account: create a #TpAccount; * see tp_client_factory_ensure_account() * @dup_account_features: implementation of tp_client_factory_dup_account_features() * @create_connection: create a #TpConnection; * see tp_client_factory_ensure_connection() * @dup_connection_features: implementation of * tp_client_factory_dup_connection_features() * @create_channel: create a #TpChannel; * see tp_client_factory_ensure_channel() * @dup_channel_features: implementation of tp_client_factory_dup_channel_features() * @create_contact: create a #TpContact; * see tp_client_factory_ensure_contact() * @dup_contact_features: implementation of tp_client_factory_dup_contact_features() * @create_protocol: create a #TpProtocol; * see tp_client_factory_ensure_protocol() * @dup_protocol_features: implementation of tp_client_factory_dup_protocol_features() * @create_tls_certificate: create a #TpTLSCertificate; * see tp_client_factory_ensure_tls_certificate() * @dup_tls_certificate_features: implementation of * tp_client_factory_dup_tls_certificate_features() * * The class structure for #TpClientFactory. * * #TpClientFactory maintains a cache of previously-constructed proxy * objects, so the implementations of @create_account, * @create_connection, @create_channel, @create_contact and @create_protocol * may assume that a * new object should be created when they are called. The default * implementations create unadorned instances of the relevant classes; * subclasses of the factory may choose to create more interesting proxy * subclasses. * * The default implementation of @dup_channel_features returns * #TP_CHANNEL_FEATURE_CORE, plus all features passed to * tp_client_factory_add_channel_features() by the application. * Subclasses may override this method to prepare more interesting features * from subclasses of #TpChannel, for instance. The default implementations of * the other dup_x_features methods behave similarly. * * Since: 0.99.1 */ #include "config.h" #include "telepathy-glib/client-factory.h" #include #include #include #define DEBUG_FLAG TP_DEBUG_CLIENT #include "telepathy-glib/connection-internal.h" #include "telepathy-glib/contact-internal.h" #include "telepathy-glib/debug-internal.h" #include "telepathy-glib/client-factory-internal.h" #include "telepathy-glib/protocol-internal.h" #include "telepathy-glib/util-internal.h" #include "telepathy-glib/variant-util.h" struct _TpClientFactoryPrivate { TpDBusDaemon *dbus; /* Owned object-path -> weakref to TpProxy */ GHashTable *proxy_cache; GArray *desired_account_features; GArray *desired_connection_features; GArray *desired_channel_features; GArray *desired_contact_features; GArray *desired_protocol_features; GArray *desired_tls_certificate_features; }; enum { PROP_DBUS_DAEMON = 1, N_PROPS }; G_DEFINE_TYPE (TpClientFactory, tp_client_factory, G_TYPE_OBJECT) static void proxy_invalidated_cb (TpProxy *proxy, guint domain, gint code, gchar *message, TpClientFactory *self) { g_hash_table_remove (self->priv->proxy_cache, tp_proxy_get_object_path (proxy)); } static void insert_proxy (TpClientFactory *self, gpointer proxy) { if (proxy == NULL) return; g_hash_table_insert (self->priv->proxy_cache, (gpointer) tp_proxy_get_object_path (proxy), proxy); /* This assume that invalidated signal is emitted from TpProxy dispose. May * change in a future API break? */ tp_g_signal_connect_object (proxy, "invalidated", G_CALLBACK (proxy_invalidated_cb), self, 0); } static gpointer lookup_proxy (TpClientFactory *self, const gchar *object_path) { return g_hash_table_lookup (self->priv->proxy_cache, object_path); } void _tp_client_factory_insert_proxy (TpClientFactory *self, gpointer proxy) { g_return_if_fail (lookup_proxy (self, tp_proxy_get_object_path (proxy)) == NULL); insert_proxy (self, proxy); } static TpAccount * create_account_impl (TpClientFactory *self, const gchar *object_path, GVariant *immutable_properties G_GNUC_UNUSED, GError **error) { return _tp_account_new_with_factory (self, self->priv->dbus, object_path, error); } static GArray * dup_account_features_impl (TpClientFactory *self, TpAccount *account) { return _tp_quark_array_copy ( (GQuark *) self->priv->desired_account_features->data); } static TpConnection * create_connection_impl (TpClientFactory *self, const gchar *object_path, GVariant *immutable_properties, GError **error) { return _tp_connection_new_with_factory (self, self->priv->dbus, NULL, object_path, error); } static GArray * dup_connection_features_impl (TpClientFactory *self, TpConnection *connection) { return _tp_quark_array_copy ( (GQuark *) self->priv->desired_connection_features->data); } static TpChannel * create_channel_impl (TpClientFactory *self, TpConnection *conn, const gchar *object_path, GVariant *immutable_properties, GError **error) { TpChannel *channel; GHashTable *props; props = tp_asv_from_vardict (immutable_properties); channel = _tp_channel_new_with_factory (self, conn, object_path, props, error); g_hash_table_unref (props); return channel; } static GArray * dup_channel_features_impl (TpClientFactory *self, TpChannel *channel) { return _tp_quark_array_copy ( (GQuark *) self->priv->desired_channel_features->data); } static TpContact * create_contact_impl (TpClientFactory *self, TpConnection *connection, TpHandle handle, const gchar *identifier) { return _tp_contact_new (connection, handle, identifier); } static GArray * dup_contact_features_impl (TpClientFactory *self, TpConnection *connection) { return _tp_quark_array_copy ( (GQuark *) self->priv->desired_contact_features->data); } static void tp_client_factory_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec) { TpClientFactory *self = (TpClientFactory *) object; switch (property_id) { case PROP_DBUS_DAEMON: g_value_set_object (value, self->priv->dbus); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void tp_client_factory_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec) { TpClientFactory *self = (TpClientFactory *) object; switch (property_id) { case PROP_DBUS_DAEMON: g_assert (self->priv->dbus == NULL); /* construct only */ self->priv->dbus = g_value_dup_object (value); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); break; } } static void tp_client_factory_constructed (GObject *object) { TpClientFactory *self = (TpClientFactory *) object; if (self->priv->dbus == NULL) self->priv->dbus = tp_dbus_daemon_dup (NULL); G_OBJECT_CLASS (tp_client_factory_parent_class)->constructed (object); } static void tp_client_factory_finalize (GObject *object) { TpClientFactory *self = (TpClientFactory *) object; g_clear_object (&self->priv->dbus); tp_clear_pointer (&self->priv->proxy_cache, g_hash_table_unref); tp_clear_pointer (&self->priv->desired_account_features, g_array_unref); tp_clear_pointer (&self->priv->desired_connection_features, g_array_unref); tp_clear_pointer (&self->priv->desired_channel_features, g_array_unref); tp_clear_pointer (&self->priv->desired_contact_features, g_array_unref); tp_clear_pointer (&self->priv->desired_protocol_features, g_array_unref); tp_clear_pointer (&self->priv->desired_tls_certificate_features, g_array_unref); G_OBJECT_CLASS (tp_client_factory_parent_class)->finalize (object); } static void tp_client_factory_init (TpClientFactory *self) { GQuark feature; self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, TP_TYPE_CLIENT_FACTORY, TpClientFactoryPrivate); self->priv->proxy_cache = g_hash_table_new (g_str_hash, g_str_equal); self->priv->desired_account_features = g_array_new (TRUE, FALSE, sizeof (GQuark)); feature = TP_ACCOUNT_FEATURE_CORE; g_array_append_val (self->priv->desired_account_features, feature); self->priv->desired_connection_features = g_array_new (TRUE, FALSE, sizeof (GQuark)); feature = TP_CONNECTION_FEATURE_CORE; g_array_append_val (self->priv->desired_connection_features, feature); self->priv->desired_channel_features = g_array_new (TRUE, FALSE, sizeof (GQuark)); feature = TP_CHANNEL_FEATURE_CORE; g_array_append_val (self->priv->desired_channel_features, feature); self->priv->desired_contact_features = g_array_new (TRUE, FALSE, sizeof (GQuark)); self->priv->desired_protocol_features = g_array_new (TRUE, FALSE, sizeof (GQuark)); feature = TP_PROTOCOL_FEATURE_CORE; g_array_append_val (self->priv->desired_protocol_features, feature); self->priv->desired_tls_certificate_features = g_array_new (TRUE, FALSE, sizeof (GQuark)); feature = TP_TLS_CERTIFICATE_FEATURE_CORE; g_array_append_val (self->priv->desired_tls_certificate_features, feature); } static TpProtocol * create_protocol_impl (TpClientFactory *self, const gchar *cm_name, const gchar *protocol_name, GVariant *immutable_properties G_GNUC_UNUSED, GError **error) { return _tp_protocol_new (self->priv->dbus, self, cm_name, protocol_name, immutable_properties, error); } static GArray * dup_protocol_features_impl (TpClientFactory *self, TpProtocol *protocol) { return _tp_quark_array_copy ( (GQuark *) self->priv->desired_protocol_features->data); } static TpTLSCertificate * create_tls_certificate_impl (TpClientFactory *self, TpProxy *conn_or_chan, const gchar *object_path, GError **error) { return _tp_tls_certificate_new (conn_or_chan, object_path, error); } static GArray * dup_tls_certificate_features_impl (TpClientFactory *self, TpTLSCertificate *certificate) { return _tp_quark_array_copy ( (GQuark *) self->priv->desired_tls_certificate_features->data); } static void tp_client_factory_class_init (TpClientFactoryClass *klass) { GObjectClass *object_class = (GObjectClass *) klass; GParamSpec *param_spec; g_type_class_add_private (klass, sizeof (TpClientFactoryPrivate)); object_class->get_property = tp_client_factory_get_property; object_class->set_property = tp_client_factory_set_property; object_class->constructed = tp_client_factory_constructed; object_class->finalize = tp_client_factory_finalize; klass->create_account = create_account_impl; klass->dup_account_features = dup_account_features_impl; klass->create_connection = create_connection_impl; klass->dup_connection_features = dup_connection_features_impl; klass->create_channel = create_channel_impl; klass->dup_channel_features = dup_channel_features_impl; klass->create_contact = create_contact_impl; klass->dup_contact_features = dup_contact_features_impl; klass->create_protocol = create_protocol_impl; klass->dup_protocol_features = dup_protocol_features_impl; klass->create_tls_certificate = create_tls_certificate_impl; klass->dup_tls_certificate_features = dup_tls_certificate_features_impl; /** * TpClientFactory:dbus-daemon: * * The D-Bus daemon for this object. */ param_spec = g_param_spec_object ("dbus-daemon", "D-Bus daemon", "The D-Bus daemon used by this object", 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); } /** * tp_client_factory_new: * @dbus: (allow-none): a #TpDBusDaemon, or %NULL * * Creates a new #TpClientFactory instance. If @dbus is %NULL then * tp_dbus_daemon_dup() will be used. * * Returns: a new #TpClientFactory * * Since: 0.99.1 */ TpClientFactory * tp_client_factory_new (TpDBusDaemon *dbus) { g_return_val_if_fail (dbus == NULL || TP_IS_DBUS_DAEMON (dbus), NULL); return g_object_new (TP_TYPE_CLIENT_FACTORY, "dbus-daemon", dbus, NULL); } static GWeakRef singleton; /** * tp_client_factory_dup: * @error: Used to raise an error if getting the session #GDBusConnection fails * * Get a reference to a #TpClientFactory singleton. It can fail and block only * if the session #GDBusConnection singleton doesn't exist yet. It is thus * recommended to call g_bus_get() before using a #TpClientFactory if the * application must not block. * * By default it will create a #TpAutomaticClientFactory. * * Returns: (transfer full): a reference to a #TpClientFactory singleton. * Since: 0.UNRELEASED */ TpClientFactory * tp_client_factory_dup (GError **error) { TpClientFactory *self; self = g_weak_ref_get (&singleton); if (self == NULL) { TpDBusDaemon *dbus; dbus = tp_dbus_daemon_dup (error); if (dbus == NULL) return NULL; self = tp_automatic_client_factory_new (dbus); g_weak_ref_set (&singleton, self); g_object_unref (dbus); } return self; } /** * tp_client_factory_set_default: * @self: a #TpClientFactory * * Define the #TpClientFactory singleton that will be returned by * tp_client_factory_dup(). * * This function may only be called before the first call to * tp_client_factory_dup(), and may not be called more than once. Applications * which use a custom #TpClientFactory and want it to be the default factory * should call this. * * Only a weak reference is taken on @self. It is the caller's responsibility * to keep it alive. If @self is disposed after calling this function, the * next call to tp_client_factory_dup() will return a newly created * #TpClientFactory. * * Since: 0.UNRELEASED */ void tp_client_factory_set_default (TpClientFactory *self) { TpClientFactory *tmp; g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); tmp = g_weak_ref_get (&singleton); if (tmp != NULL) { CRITICAL ("tp_client_factory_set_default() may only be called once and" "before first call of tp_client_factory_dup()"); g_object_unref (tmp); g_return_if_reached (); } g_weak_ref_set (&singleton, self); } /** * tp_client_factory_can_set_default: * * Check if tp_client_factory_set_default() has already successfully been * called. * * Returns: %TRUE if tp_client_factory_set_default() has already successfully * been called in this process, %FALSE otherwise. * * Since: 0.UNRELEASED */ gboolean tp_client_factory_can_set_default (void) { TpClientFactory *tmp; gboolean ret; tmp = g_weak_ref_get (&singleton); ret = (tmp == NULL); g_clear_object (&tmp); return ret; } /** * tp_client_factory_get_dbus_daemon: * @self: a #TpClientFactory object * * * * Returns: (transfer none): the value of the #TpClientFactory:dbus-daemon * property * * Since: 0.99.1 */ TpDBusDaemon * tp_client_factory_get_dbus_daemon (TpClientFactory *self) { g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); return self->priv->dbus; } /** * tp_client_factory_get_dbus_connection: * @self: a #TpClientFactory object * * * * Returns: (transfer none): the #TpClientFactory:dbus-daemon's * #GDBusConnection. * * Since: 0.UNRELEASED */ GDBusConnection * tp_client_factory_get_dbus_connection (TpClientFactory *self) { g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); return tp_proxy_get_dbus_connection (self->priv->dbus); } /** * tp_client_factory_ensure_account_manager: * @self: a #TpClientFactory object * * * * Returns: (transfer full): a reference to a #TpAccountManager singleton. * * Since: 0.UNRELEASED */ TpAccountManager * tp_client_factory_ensure_account_manager (TpClientFactory *self) { TpAccountManager *account_manager; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); account_manager = lookup_proxy (self, TP_ACCOUNT_MANAGER_OBJECT_PATH); if (account_manager != NULL) return g_object_ref (account_manager); account_manager = _tp_account_manager_new (self); insert_proxy (self, account_manager); return account_manager; } /** * tp_client_factory_ensure_channel_dispatcher: * @self: a #TpClientFactory object * * * * Returns: (transfer full): a reference to a #TpChannelDispatcher singleton. * * Since: 0.UNRELEASED */ TpChannelDispatcher * tp_client_factory_ensure_channel_dispatcher (TpClientFactory *self) { TpChannelDispatcher *channel_dispatcher; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); channel_dispatcher = lookup_proxy (self, TP_CHANNEL_DISPATCHER_OBJECT_PATH); if (channel_dispatcher != NULL) return g_object_ref (channel_dispatcher); channel_dispatcher = _tp_channel_dispatcher_new (self); insert_proxy (self, channel_dispatcher); return channel_dispatcher; } /** * tp_client_factory_ensure_account: * @self: a #TpClientFactory object * @object_path: the object path of an account * @immutable_properties: (allow-none): a #G_VARIANT_TYPE_VARDICT containing * the immutable properties of the account, or %NULL. * @error: Used to raise an error if @object_path is not valid * * Returns a #TpAccount proxy for the account at @object_path. The returned * #TpAccount is cached; the same #TpAccount object will be returned by this * function repeatedly, as long as at least one reference exists. * * Note that the returned #TpAccount is not guaranteed to be ready; the caller * is responsible for calling tp_proxy_prepare_async() with the desired * features (as given by tp_client_factory_dup_account_features()). * * This function is rather low-level. tp_account_manager_dup_usable_accounts() * and #TpAccountManager::usability-changed are more appropriate for most * applications. * * @immutable_properties is consumed if it is floating. * * Returns: (transfer full): a reference to a #TpAccount; * see tp_account_new(). * * Since: 0.99.1 */ TpAccount * tp_client_factory_ensure_account (TpClientFactory *self, const gchar *object_path, GVariant *immutable_properties, GError **error) { TpAccount *account; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); if (immutable_properties == NULL) immutable_properties = g_variant_new ("a{sv}", NULL); g_variant_ref_sink (immutable_properties); account = lookup_proxy (self, object_path); if (account != NULL) { g_object_ref (account); } else { account = TP_CLIENT_FACTORY_GET_CLASS (self)->create_account (self, object_path, immutable_properties, error); insert_proxy (self, account); } g_variant_unref (immutable_properties); return account; } /** * tp_client_factory_dup_account_features: * @self: a #TpClientFactory object * @account: a #TpAccount * * Return a zero-terminated #GArray containing the #TpAccount features that * should be prepared on @account. * * Returns: (transfer full) (element-type GLib.Quark): a newly allocated * #GArray * * Since: 0.99.1 */ GArray * tp_client_factory_dup_account_features (TpClientFactory *self, TpAccount *account) { g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (TP_IS_ACCOUNT (account), NULL); g_return_val_if_fail (tp_proxy_get_factory (account) == self, NULL); return TP_CLIENT_FACTORY_GET_CLASS (self)->dup_account_features (self, account); } /** * tp_client_factory_add_account_features: * @self: a #TpClientFactory object * @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 * * Add @features to the desired features to be prepared on #TpAccount * objects. Those features will be added to the features already returned be * tp_client_factory_dup_account_features(). * * It is not necessary to add %TP_ACCOUNT_FEATURE_CORE as it is already * included by default. * * Note that these features will not be added to existing #TpAccount * objects; the user must call tp_proxy_prepare_async() themself. * * Since: 0.99.1 */ void tp_client_factory_add_account_features ( TpClientFactory *self, const GQuark *features) { g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); _tp_quark_array_merge (self->priv->desired_account_features, features, -1); } /** * tp_client_factory_add_account_features_varargs: (skip) * @self: a #TpClientFactory * @feature: the first feature * @...: the second and subsequent features, if any, ending with 0 * * The same as tp_client_factory_add_account_features(), but with a more * convenient calling convention from C. * * Since: 0.99.1 */ void tp_client_factory_add_account_features_varargs ( TpClientFactory *self, GQuark feature, ...) { va_list var_args; g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); va_start (var_args, feature); _tp_quark_array_merge_valist (self->priv->desired_account_features, feature, var_args); va_end (var_args); } /** * tp_client_factory_ensure_connection: * @self: a #TpClientFactory object * @object_path: the object path of a connection * @immutable_properties: (allow-none): a #G_VARIANT_TYPE_VARDICT containing * the immutable properties of the connection, or %NULL. * @error: Used to raise an error if @object_path is not valid * * Returns a #TpConnection proxy for the connection at @object_path. * The returned #TpConnection is cached; the same #TpConnection object * will be returned by this function repeatedly, as long as at least one * reference exists. * * Note that the returned #TpConnection is not guaranteed to be ready; the * caller is responsible for calling tp_proxy_prepare_async() with the desired * features (as given by tp_client_factory_dup_connection_features()). * * This function is rather low-level. #TpAccount:connection is more * appropriate for most applications. * * @immutable_properties is consumed if it is floating. * * Returns: (transfer full): a reference to a #TpConnection; * see tp_connection_new(). * * Since: 0.99.1 */ TpConnection * tp_client_factory_ensure_connection (TpClientFactory *self, const gchar *object_path, GVariant *immutable_properties, GError **error) { TpConnection *connection; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); if (immutable_properties == NULL) immutable_properties = g_variant_new ("a{sv}", NULL); g_variant_ref_sink (immutable_properties); connection = lookup_proxy (self, object_path); if (connection != NULL) { g_object_ref (connection); } else { connection = TP_CLIENT_FACTORY_GET_CLASS (self)->create_connection ( self, object_path, immutable_properties, error); insert_proxy (self, connection); } g_variant_unref (immutable_properties); return connection; } /** * tp_client_factory_dup_connection_features: * @self: a #TpClientFactory object * @connection: a #TpConnection * * Return a zero-terminated #GArray containing the #TpConnection features that * should be prepared on @connection. * * Returns: (transfer full) (element-type GLib.Quark): a newly allocated * #GArray * * Since: 0.99.1 */ GArray * tp_client_factory_dup_connection_features (TpClientFactory *self, TpConnection *connection) { g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL); g_return_val_if_fail (tp_proxy_get_factory (connection) == self, NULL); return TP_CLIENT_FACTORY_GET_CLASS (self)->dup_connection_features ( self, connection); } /** * tp_client_factory_add_connection_features: * @self: a #TpClientFactory object * @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 * * Add @features to the desired features to be prepared on #TpConnection * objects. Those features will be added to the features already returned be * tp_client_factory_dup_connection_features(). * * It is not necessary to add %TP_CONNECTION_FEATURE_CORE as it is already * included by default. * * Note that these features will not be added to existing #TpConnection * objects; the user must call tp_proxy_prepare_async() themself. * * Since: 0.99.1 */ void tp_client_factory_add_connection_features ( TpClientFactory *self, const GQuark *features) { g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); _tp_quark_array_merge (self->priv->desired_connection_features, features, -1); } /** * tp_client_factory_add_connection_features_varargs: (skip) * @self: a #TpClientFactory * @feature: the first feature * @...: the second and subsequent features, if any, ending with 0 * * The same as tp_client_factory_add_connection_features(), but with a * more convenient calling convention from C. * * Since: 0.99.1 */ void tp_client_factory_add_connection_features_varargs ( TpClientFactory *self, GQuark feature, ...) { va_list var_args; g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); va_start (var_args, feature); _tp_quark_array_merge_valist (self->priv->desired_connection_features, feature, var_args); va_end (var_args); } /** * tp_client_factory_ensure_channel: * @self: a #TpClientFactory * @connection: a #TpConnection whose #TpProxy:factory is this object * @object_path: the object path of a channel on @connection * @immutable_properties: (allow-none): a #G_VARIANT_TYPE_VARDICT containing * the immutable properties of the account, or %NULL. * @error: Used to raise an error if @object_path is not valid * * Returns a #TpChannel proxy for the channel at @object_path on @connection. * The returned #TpChannel is cached; the same #TpChannel object * will be returned by this function repeatedly, as long as at least one * reference exists. * * Note that the returned #TpChannel is not guaranteed to be ready; the * caller is responsible for calling tp_proxy_prepare_async() with the desired * features (as given by tp_client_factory_dup_channel_features()). * * This function is rather low-level. * #TpAccountChannelRequest and #TpBaseClient are more appropriate ways * to obtain channels for most applications. * * @immutable_properties is consumed if it is floating. * * Returns: (transfer full): a reference to a #TpChannel; * see tp_channel_new_from_properties(). * * Since: 0.99.1 */ TpChannel * tp_client_factory_ensure_channel (TpClientFactory *self, TpConnection *connection, const gchar *object_path, GVariant *immutable_properties, GError **error) { TpChannel *channel; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL); g_return_val_if_fail (tp_proxy_get_factory (connection) == self, NULL); g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); if (immutable_properties == NULL) immutable_properties = g_variant_new ("a{sv}", NULL); g_variant_ref_sink (immutable_properties); channel = lookup_proxy (self, object_path); if (channel != NULL) { g_object_ref (channel); } else { channel = TP_CLIENT_FACTORY_GET_CLASS (self)->create_channel (self, connection, object_path, immutable_properties, error); insert_proxy (self, channel); } g_variant_unref (immutable_properties); return channel; } /** * tp_client_factory_dup_channel_features: * @self: a #TpClientFactory object * @channel: a #TpChannel * * Return a zero-terminated #GArray containing the #TpChannel features that * should be prepared on @channel. * * Returns: (transfer full) (element-type GLib.Quark): a newly allocated * #GArray * * Since: 0.99.1 */ GArray * tp_client_factory_dup_channel_features (TpClientFactory *self, TpChannel *channel) { g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (TP_IS_CHANNEL (channel), NULL); g_return_val_if_fail (tp_proxy_get_factory (channel) == self, NULL); return TP_CLIENT_FACTORY_GET_CLASS (self)->dup_channel_features ( self, channel); } /** * tp_client_factory_add_channel_features: * @self: a #TpClientFactory object * @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 * * Add @features to the desired features to be prepared on #TpChannel * objects. Those features will be added to the features already returned be * tp_client_factory_dup_channel_features(). * * It is not necessary to add %TP_CHANNEL_FEATURE_CORE as it is already * included by default. * * Note that these features will not be added to existing #TpChannel * objects; the user must call tp_proxy_prepare_async() themself. * * Since: 0.99.1 */ void tp_client_factory_add_channel_features ( TpClientFactory *self, const GQuark *features) { g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); _tp_quark_array_merge (self->priv->desired_channel_features, features, -1); } /** * tp_client_factory_add_channel_features_varargs: (skip) * @self: a #TpClientFactory * @feature: the first feature * @...: the second and subsequent features, if any, ending with 0 * * The same as tp_client_factory_add_channel_features(), but with a * more convenient calling convention from C. * * Since: 0.99.1 */ void tp_client_factory_add_channel_features_varargs ( TpClientFactory *self, GQuark feature, ...) { va_list var_args; g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); va_start (var_args, feature); _tp_quark_array_merge_valist (self->priv->desired_channel_features, feature, var_args); va_end (var_args); } /** * tp_client_factory_ensure_contact: * @self: a #TpClientFactory object * @connection: a #TpConnection whose #TpProxy:factory is this object * @handle: a #TpHandle * @identifier: a string representing the contact's identifier * * Returns a #TpContact representing @identifier (and @handle) on @connection. * The returned #TpContact is cached; the same #TpContact object * will be returned by this function repeatedly, as long as at least one * reference exists. * * Note that the returned #TpContact is not guaranteed to be ready; the caller * is responsible for calling tp_connection_upgrade_contacts() with the desired * features (as given by tp_client_factory_dup_contact_features()). * * Returns: (transfer full): a reference to a #TpContact. * * Since: 0.99.1 */ TpContact * tp_client_factory_ensure_contact (TpClientFactory *self, TpConnection *connection, TpHandle handle, const gchar *identifier) { TpContact *contact; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL); g_return_val_if_fail (tp_proxy_get_factory (connection) == self, NULL); g_return_val_if_fail (handle != 0, NULL); g_return_val_if_fail (identifier != NULL, NULL); contact = _tp_connection_lookup_contact (connection, handle); if (contact != NULL) { g_return_val_if_fail (!tp_strdiff (tp_contact_get_identifier (contact), identifier), NULL); return g_object_ref (contact); } contact = TP_CLIENT_FACTORY_GET_CLASS (self)->create_contact (self, connection, handle, identifier); _tp_connection_add_contact (connection, handle, contact); return contact; } static void upgrade_contacts_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TpConnection *connection = (TpConnection *) source; GSimpleAsyncResult *my_result = user_data; GPtrArray *contacts; GError *error = NULL; if (!tp_connection_upgrade_contacts_finish (connection, result, &contacts, &error)) { g_simple_async_result_take_error (my_result, error); } else { g_simple_async_result_set_op_res_gpointer (my_result, contacts, (GDestroyNotify) g_ptr_array_unref); } g_simple_async_result_complete (my_result); g_object_unref (my_result); } /** * tp_client_factory_upgrade_contacts_async: * @self: a #TpClientFactory object * @connection: a #TpConnection whose #TpProxy:factory is this object * @n_contacts: The number of contacts in @contacts (must be at least 1) * @contacts: (array length=n_contacts): An array of #TpContact objects * associated with @self * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * Same as tp_connection_upgrade_contacts_async(), but prepare contacts with all * features previously passed to * tp_client_factory_add_contact_features(). * * Since: 0.19.1 */ void tp_client_factory_upgrade_contacts_async ( TpClientFactory *self, TpConnection *connection, guint n_contacts, TpContact * const *contacts, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; GArray *features; /* no real reason this shouldn't work, but it's really confusing * and probably indicates an error */ g_warn_if_fail (tp_proxy_get_factory (connection) == self); result = g_simple_async_result_new ((GObject *) self, callback, user_data, tp_client_factory_upgrade_contacts_async); features = tp_client_factory_dup_contact_features (self, connection); tp_connection_upgrade_contacts_async (connection, n_contacts, contacts, (GQuark *) features->data, upgrade_contacts_cb, result); g_array_unref (features); } /** * tp_client_factory_upgrade_contacts_finish: * @self: a #TpClientFactory * @result: a #GAsyncResult * @contacts: (element-type TelepathyGLib.Contact) (transfer container) (out) (allow-none): * a location to set a #GPtrArray of upgraded #TpContact, or %NULL. * @error: a #GError to fill * * Finishes tp_client_factory_upgrade_contacts_async() * * Returns: %TRUE on success, %FALSE otherwise. * Since: 0.19.1 */ gboolean tp_client_factory_upgrade_contacts_finish ( TpClientFactory *self, GAsyncResult *result, GPtrArray **contacts, GError **error) { _tp_implement_finish_copy_pointer (self, tp_client_factory_upgrade_contacts_async, g_ptr_array_ref, contacts); } static void dup_contact_by_id_cb (GObject *source, GAsyncResult *result, gpointer user_data) { TpConnection *connection = (TpConnection *) source; GSimpleAsyncResult *my_result = user_data; TpContact *contact; GError *error = NULL; contact = tp_connection_dup_contact_by_id_finish (connection, result, &error); if (contact == NULL) { g_simple_async_result_take_error (my_result, error); } else { g_simple_async_result_set_op_res_gpointer (my_result, contact, g_object_unref); } g_simple_async_result_complete (my_result); g_object_unref (my_result); } /** * tp_client_factory_ensure_contact_by_id_async: * @self: a #TpClientFactory object * @connection: a #TpConnection * @identifier: a string representing the contact's identifier * @callback: a callback to call when the operation finishes * @user_data: data to pass to @callback * * Same as tp_connection_dup_contact_by_id_async(), but prepare the * contact with all features previously passed to * tp_client_factory_add_contact_features(). * * Since: 0.19.1 */ void tp_client_factory_ensure_contact_by_id_async ( TpClientFactory *self, TpConnection *connection, const gchar *identifier, GAsyncReadyCallback callback, gpointer user_data) { GSimpleAsyncResult *result; GArray *features; result = g_simple_async_result_new ((GObject *) self, callback, user_data, tp_client_factory_ensure_contact_by_id_async); features = tp_client_factory_dup_contact_features (self, connection); tp_connection_dup_contact_by_id_async (connection, identifier, (GQuark *) features->data, dup_contact_by_id_cb, result); g_array_unref (features); } /** * tp_client_factory_ensure_contact_by_id_finish: * @self: a #TpClientFactory * @result: a #GAsyncResult * @error: a #GError to fill * * Finishes tp_client_factory_ensure_contact_by_id_async() * * Returns: (transfer full): a #TpContact or %NULL on error. * Since: 0.19.1 */ TpContact * tp_client_factory_ensure_contact_by_id_finish ( TpClientFactory *self, GAsyncResult *result, GError **error) { _tp_implement_finish_return_copy_pointer (self, tp_client_factory_ensure_contact_by_id_async, g_object_ref); } /** * tp_client_factory_dup_contact_features: * @self: a #TpClientFactory object * @connection: a #TpConnection * * Return a #GArray containing the contact feature #GQuarks * that should be prepared on all contacts of @connection. * * Returns: (transfer full) (element-type GLib.Quark): a newly * allocated #GArray * * Since: 0.99.1 */ GArray * tp_client_factory_dup_contact_features (TpClientFactory *self, TpConnection *connection) { g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (TP_IS_CONNECTION (connection), NULL); g_return_val_if_fail (tp_proxy_get_factory (connection) == self, NULL); return TP_CLIENT_FACTORY_GET_CLASS (self)->dup_contact_features ( self, connection); } /** * tp_client_factory_add_contact_features: * @self: a #TpClientFactory object * @features: (transfer none) (array zero-terminated=1) (allow-none): * an array of desired features * * Add @features to the desired features to be prepared on #TpContact * objects. Those features will be added to the features already returned be * tp_client_factory_dup_contact_features(). * * Note that these features will not be added to existing #TpContact * objects; the user must call tp_connection_upgrade_contacts() themself. * * Since: 0.99.1 */ void tp_client_factory_add_contact_features (TpClientFactory *self, const GQuark *features) { g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); _tp_quark_array_merge (self->priv->desired_contact_features, features, -1); } /** * tp_client_factory_add_contact_features_varargs: (skip) * @self: a #TpClientFactory * @feature: the first feature * @...: the second and subsequent features, if any, ending with 0 * * The same as tp_client_factory_add_contact_features(), but with a * more convenient calling convention from C. * * Since: 0.99.1 */ void tp_client_factory_add_contact_features_varargs ( TpClientFactory *self, GQuark feature, ...) { va_list var_args; g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); va_start (var_args, feature); _tp_quark_array_merge_valist (self->priv->desired_contact_features, feature, var_args); va_end (var_args); } /* * _tp_client_factory_ensure_channel_request: * @self: a #TpClientFactory object * @object_path: the object path of a channel request * @immutable_properties: (transfer none) (element-type utf8 GObject.Value): * the immutable properties of the channel request * @error: Used to raise an error if @object_path is not valid * * Returns a #TpChannelRequest for @object_path. The returned * #TpChannelRequest is cached; the same #TpChannelRequest object will be * returned by this function repeatedly, as long as at least one reference * exists. * * Note that the returned #TpChannelRequest is not guaranteed to be ready; the * caller is responsible for calling tp_proxy_prepare_async(). * * Returns: (transfer full): a reference to a #TpChannelRequest; * see tp_channel_request_new(). * * Since: 0.99.1 */ TpChannelRequest * _tp_client_factory_ensure_channel_request (TpClientFactory *self, const gchar *object_path, GHashTable *immutable_properties, GError **error) { TpChannelRequest *request; GVariant *props; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); g_return_val_if_fail (immutable_properties != NULL, NULL); request = lookup_proxy (self, object_path); if (request != NULL) return g_object_ref (request); props = tp_asv_to_vardict (immutable_properties); g_variant_ref_sink (props); request = _tp_channel_request_new_with_factory (self, self->priv->dbus, object_path, props, error); g_variant_unref (props); insert_proxy (self, request); return request; } /* * _tp_client_factory_ensure_channel_dispatch_operation: * @self: a #TpClientFactory object * @object_path: the object path of a channel dispatch operation * @immutable_properties: (transfer none) (element-type utf8 GObject.Value): * the immutable properties of the channel dispatch operation * @error: Used to raise an error if @object_path is not valid * * Returns a #TpChannelDispatchOperation for @object_path. * The returned #TpChannelDispatchOperation is cached; the same * #TpChannelDispatchOperation object will be returned by this function * repeatedly, as long as at least one reference exists. * * Note that the returned #TpChannelDispatchOperation is not guaranteed to be * ready; the caller is responsible for calling tp_proxy_prepare_async(). * * Returns: (transfer full): a reference to a * #TpChannelDispatchOperation; see tp_channel_dispatch_operation_new(). * * Since: 0.99.1 */ TpChannelDispatchOperation * _tp_client_factory_ensure_channel_dispatch_operation ( TpClientFactory *self, const gchar *object_path, GHashTable *immutable_properties, GError **error) { TpChannelDispatchOperation *dispatch; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (g_variant_is_object_path (object_path), NULL); dispatch = lookup_proxy (self, object_path); if (dispatch != NULL) return g_object_ref (dispatch); dispatch = _tp_channel_dispatch_operation_new_with_factory (self, self->priv->dbus, object_path, immutable_properties, error); insert_proxy (self, dispatch); return dispatch; } /** * tp_client_factory_ensure_protocol: * @self: a #TpClientFactory * @cm_name: the connection manager name (such as "gabble") * @protocol_name: the protocol name (such as "jabber") * @immutable_properties: (allow-none): a #G_VARIANT_TYPE_VARDICT containing * the immutable properties of the protocol, or %NULL. * @error: Used to raise an error if @cm_name or @protocol_name is invalid * * Returns a #TpProtocol proxy for the protocol @protocol_name on connection * manager @cm_name. * The returned #TpProtocol is cached; the same #TpProtocol object * will be returned by this function repeatedly, as long as at least one * reference exists. * * Note that the returned #TpProtocol is not guaranteed to be ready; the * caller is responsible for calling tp_proxy_prepare_async() with the desired * features (as given by tp_client_factory_dup_protocol_features()). * * @immutable_properties is consumed if it is floating. * * Returns: (transfer full): a reference to a #TpProtocol, * or %NULL on invalid arguments * * Since: 0.99.8 */ TpProtocol * tp_client_factory_ensure_protocol (TpClientFactory *self, const gchar *cm_name, const gchar *protocol_name, GVariant *immutable_properties, GError **error) { TpProtocol *protocol; gchar *object_path; g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); if (immutable_properties == NULL) immutable_properties = g_variant_new ("a{sv}", NULL); g_variant_ref_sink (immutable_properties); object_path = _tp_protocol_build_object_path (cm_name, protocol_name); protocol = lookup_proxy (self, object_path); if (protocol != NULL) { g_object_ref (protocol); } else { protocol = TP_CLIENT_FACTORY_GET_CLASS (self)->create_protocol (self, cm_name, protocol_name, immutable_properties, error); if (protocol != NULL) { g_assert (g_str_equal (tp_proxy_get_object_path (protocol), object_path)); insert_proxy (self, protocol); } } g_variant_unref (immutable_properties); g_free (object_path); return protocol; } /** * tp_client_factory_dup_protocol_features: * @self: a #TpClientFactory object * @protocol: a #TpProtocol * * Return a zero-terminated #GArray containing the #TpProtocol features that * should be prepared on @protocol. * * Returns: (transfer full) (element-type GLib.Quark): a newly allocated * #GArray * * Since: 0.99.8 */ GArray * tp_client_factory_dup_protocol_features (TpClientFactory *self, TpProtocol *protocol) { g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (TP_IS_PROTOCOL (protocol), NULL); g_return_val_if_fail (tp_proxy_get_factory (protocol) == self, NULL); return TP_CLIENT_FACTORY_GET_CLASS (self)->dup_protocol_features ( self, protocol); } /** * tp_client_factory_add_protocol_features: * @self: a #TpClientFactory object * @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 * * Add @features to the desired features to be prepared on #TpProtocol * objects. Those features will be added to the features already returned be * tp_client_factory_dup_protocol_features(). * * It is not necessary to add %TP_PROTOCOL_FEATURE_CORE as it is already * included by default. * * Note that these features will not be added to existing #TpProtocol * objects; the user must call tp_proxy_prepare_async() themself. * * Since: 0.99.8 */ void tp_client_factory_add_protocol_features ( TpClientFactory *self, const GQuark *features) { g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); _tp_quark_array_merge (self->priv->desired_protocol_features, features, -1); } /** * tp_client_factory_add_protocol_features_varargs: (skip) * @self: a #TpClientFactory * @feature: the first feature * @...: the second and subsequent features, if any, ending with 0 * * The same as tp_client_factory_add_protocol_features(), but with a * more convenient calling convention from C. * * Since: 0.99.8 */ void tp_client_factory_add_protocol_features_varargs ( TpClientFactory *self, GQuark feature, ...) { va_list var_args; g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); va_start (var_args, feature); _tp_quark_array_merge_valist (self->priv->desired_protocol_features, feature, var_args); va_end (var_args); } /** * tp_client_factory_ensure_tls_certificate: * @self: a #TpClientFactory * @conn_or_chan: a #TpConnection or #TpChannel parent for this object, whose * invalidation will also result in invalidation of the returned object * @object_path: the object path of this TLS certificate * @error: Used to raise an error * * Returns a #TpTLSCertificate proxy for the channel or connection * @conn_or_chan. * The returned #TpTLSCertificate is cached; the same #TpTLSCertificate object * will be returned by this function repeatedly, as long as at least one * reference exists. * * Note that the returned #TpTLSCertificate is not guaranteed to be ready; the * caller is responsible for calling tp_proxy_prepare_async() with the desired * features (as given by tp_client_factory_dup_tls_certificate_features()). * * Returns: (transfer full): a reference to a #TpTLSCertificate, * or %NULL on invalid arguments * * Since: 0.99.8 */ TpTLSCertificate * tp_client_factory_ensure_tls_certificate (TpClientFactory *self, TpProxy *conn_or_chan, const gchar *object_path, GError **error) { TpTLSCertificate *cert; g_return_val_if_fail (tp_proxy_get_factory (conn_or_chan) == self, NULL); cert = lookup_proxy (self, object_path); if (cert != NULL) { g_object_ref (cert); } else { cert = TP_CLIENT_FACTORY_GET_CLASS (self)->create_tls_certificate (self, conn_or_chan, object_path, error); insert_proxy (self, cert); } return cert; } /** * tp_client_factory_dup_tls_certificate_features: * @self: a #TpClientFactory object * @certificate: a #TpTLSCertificate * * Return a zero-terminated #GArray containing the #TpTLSCertificate features * that should be prepared on @protocol. * * Returns: (transfer full) (element-type GLib.Quark): a newly allocated * #GArray * * Since: 0.99.8 */ GArray * tp_client_factory_dup_tls_certificate_features (TpClientFactory *self, TpTLSCertificate *certificate) { g_return_val_if_fail (TP_IS_CLIENT_FACTORY (self), NULL); g_return_val_if_fail (TP_IS_TLS_CERTIFICATE (certificate), NULL); g_return_val_if_fail (tp_proxy_get_factory (certificate) == self, NULL); return TP_CLIENT_FACTORY_GET_CLASS (self)->dup_tls_certificate_features ( self, certificate); } /** * tp_client_factory_add_tls_certificate_features: * @self: a #TpClientFactory object * @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 * * Add @features to the desired features to be prepared on #TpTLSCertificate * objects. Those features will be added to the features already returned be * tp_client_factory_dup_tls_certificate_features(). * * It is not necessary to add %TP_TLS_CERTIFICATE_FEATURE_CORE as it is already * included by default. * * Note that these features will not be added to existing #TpTLSCertificate * objects; the user must call tp_proxy_prepare_async() themself. * * Since: 0.99.8 */ void tp_client_factory_add_tls_certificate_features (TpClientFactory *self, const GQuark *features) { g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); _tp_quark_array_merge (self->priv->desired_tls_certificate_features, features, -1); } /** * tp_client_factory_add_tls_certificate_features_varargs: (skip) * @self: a #TpClientFactory * @feature: the first feature * @...: the second and subsequent features, if any, ending with 0 * * The same as tp_client_factory_add_tls_certificate_features(), but with a * more convenient calling convention from C. * * Since: 0.99.8 */ void tp_client_factory_add_tls_certificate_features_varargs ( TpClientFactory *self, GQuark feature, ...) { va_list var_args; g_return_if_fail (TP_IS_CLIENT_FACTORY (self)); va_start (var_args, feature); _tp_quark_array_merge_valist (self->priv->desired_tls_certificate_features, feature, var_args); va_end (var_args); }