diff -r 000000000000 -r d0f3a028347a telepathygabble/src/vcard-manager.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/telepathygabble/src/vcard-manager.c Tue Feb 02 01:10:06 2010 +0200 @@ -0,0 +1,956 @@ +/* + * vcard-manager.c - Source for Gabble vCard lookup helper + * + * Copyright (C) 2006 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 + */ + + +#include +#include + +#include "debug.h" +#include "gabble-connection.h" +#include "namespaces.h" +#include "telepathy-helpers.h" +#include "util.h" +#include "vcard-manager.h" + +#include "gabble_enums.h" + +#define DBUS_API_SUBJECT_TO_CHANGE +#define DEBUG_FLAG GABBLE_DEBUG_VCARD +#define DEFAULT_REQUEST_TIMEOUT 20000 + +#ifdef DEBUG_FLAG +//#define DEBUG(format, ...) +#define DEBUGGING 0 +#define NODE_DEBUG(n, s) +#endif /* DEBUG_FLAG */ + + +#ifndef EMULATOR +G_DEFINE_TYPE(GabbleVCardManager, gabble_vcard_manager, G_TYPE_OBJECT); +#endif + +/* signal enum */ +enum +{ + NICKNAME_UPDATE, + LAST_SIGNAL +#ifdef EMULATOR + = LAST_SIGNAL_VCARD_MGR +#endif + +}; + +#ifdef EMULATOR +#include "libgabble_wsd_solution.h" + + GET_STATIC_ARRAY_FROM_TLS(signals,gabble_vcard_mgr,guint) + #define signals (GET_WSD_VAR_NAME(signals,gabble_vcard_mgr, s)()) + + GET_STATIC_VAR_FROM_TLS(quark1,gabble_vcard_mgr,GQuark) + #define quark1 (*GET_WSD_VAR_NAME(quark1,gabble_vcard_mgr, s)()) + + GET_STATIC_VAR_FROM_TLS(quark2,gabble_vcard_mgr,GQuark) + #define quark2 (*GET_WSD_VAR_NAME(quark2,gabble_vcard_mgr, s)()) + + GET_STATIC_VAR_FROM_TLS(gabble_vcard_manager_parent_class,gabble_vcard_mgr,gpointer) + #define gabble_vcard_manager_parent_class (*GET_WSD_VAR_NAME(gabble_vcard_manager_parent_class,gabble_vcard_mgr,s)()) + + GET_STATIC_VAR_FROM_TLS(g_define_type_id,gabble_vcard_mgr,GType) + #define g_define_type_id (*GET_WSD_VAR_NAME(g_define_type_id,gabble_vcard_mgr,s)()) + + GET_STATIC_ARRAY_FROM_TLS(NO_ALIAS,gabble_vcard_mgr,gchar) + #define NO_ALIAS (GET_WSD_VAR_NAME(NO_ALIAS,gabble_vcard_mgr,s)()) + +static void gabble_vcard_manager_init (GabbleVCardManager *self); +static void gabble_vcard_manager_class_init (GabbleVCardManagerClass *klass); +static void gabble_vcard_manager_class_intern_init (gpointer klass) + { + gabble_vcard_manager_parent_class = g_type_class_peek_parent (klass); + gabble_vcard_manager_class_init ((GabbleVCardManagerClass*) klass); + } + EXPORT_C GType gabble_vcard_manager_get_type (void) + { + if ((g_define_type_id == 0)) { static const GTypeInfo g_define_type_info = { sizeof (GabbleVCardManagerClass), (GBaseInitFunc) ((void *)0), (GBaseFinalizeFunc) ((void *)0), (GClassInitFunc) gabble_vcard_manager_class_intern_init, (GClassFinalizeFunc) ((void *)0), ((void *)0), sizeof (GabbleVCardManager), 0, (GInstanceInitFunc) gabble_vcard_manager_init, ((void *)0) }; g_define_type_id = g_type_register_static ( ((GType) ((20) << (2))), g_intern_static_string ("GabbleVCardManager"), &g_define_type_info, (GTypeFlags) 0); { {} ; } } return g_define_type_id; } ; + +#else + + static guint signals[LAST_SIGNAL] = {0}; + + static const gchar *NO_ALIAS = "none"; + +#endif + + +/* Properties */ +enum +{ + PROP_CONNECTION = 1, + LAST_PROPERTY +}; + + + +typedef struct _GabbleVCardManagerPrivate GabbleVCardManagerPrivate; +struct _GabbleVCardManagerPrivate +{ + GabbleConnection *connection; + GList *requests; + gboolean dispose_has_run; +}; + +struct _GabbleVCardManagerRequest +{ + GabbleVCardManager *manager; + guint timer_id; + guint timeout; + + GabbleHandle handle; + gchar **edit_args; + + GabbleVCardManagerCb callback; + gpointer user_data; + GObject *bound_object; +}; + +GQuark +gabble_vcard_manager_error_quark (void) +{ + +#ifndef EMULATOR + static GQuark quark1 = 0; +#endif + + if (!quark1) + quark1 = g_quark_from_static_string ("gabble-vcard-manager-error"); + return quark1; +} + +GQuark +gabble_vcard_manager_cache_quark (void) +{ + +#ifndef EMULATOR + static GQuark quark2 = 0; +#endif + + if (!quark2) + quark2 = g_quark_from_static_string ("gabble-vcard-manager-cache"); + return quark2; +} + +#define GABBLE_VCARD_MANAGER_GET_PRIVATE(o) ((GabbleVCardManagerPrivate*)((o)->priv)); + +static void +gabble_vcard_manager_init (GabbleVCardManager *obj) +{ + GabbleVCardManagerPrivate *priv = + G_TYPE_INSTANCE_GET_PRIVATE (obj, GABBLE_TYPE_VCARD_MANAGER, GabbleVCardManagerPrivate); + obj->priv = priv; + +} + +static void gabble_vcard_manager_set_property (GObject *object, guint property_id, + const GValue *value, GParamSpec *pspec); +static void gabble_vcard_manager_get_property (GObject *object, guint property_id, + GValue *value, GParamSpec *pspec); +static void gabble_vcard_manager_dispose (GObject *object); +static void gabble_vcard_manager_finalize (GObject *object); + +static void +gabble_vcard_manager_class_init (GabbleVCardManagerClass *gabble_vcard_manager_class) +{ + GObjectClass *object_class = G_OBJECT_CLASS (gabble_vcard_manager_class); + GParamSpec *param_spec; + + g_type_class_add_private (gabble_vcard_manager_class, sizeof (GabbleVCardManagerPrivate)); + + object_class->get_property = gabble_vcard_manager_get_property; + object_class->set_property = gabble_vcard_manager_set_property; + + object_class->dispose = gabble_vcard_manager_dispose; + object_class->finalize = gabble_vcard_manager_finalize; + + param_spec = g_param_spec_object ("connection", "GabbleConnection object", + "Gabble connection object that owns this " + "vCard lookup helper object.", + GABBLE_TYPE_CONNECTION, + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_READWRITE | + G_PARAM_STATIC_NICK | + G_PARAM_STATIC_BLURB); + g_object_class_install_property (object_class, PROP_CONNECTION, param_spec); + + /* signal definitions */ + + signals[NICKNAME_UPDATE] = + g_signal_new ("nickname-update", + G_TYPE_FROM_CLASS (gabble_vcard_manager_class), + G_SIGNAL_RUN_LAST, + 0, + NULL, NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, G_TYPE_UINT); + +} + +static void +gabble_vcard_manager_get_property (GObject *object, + guint property_id, + GValue *value, + GParamSpec *pspec) +{ + GabbleVCardManager *chan = GABBLE_VCARD_MANAGER (object); + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (chan); + + switch (property_id) { + case PROP_CONNECTION: + g_value_set_object (value, priv->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void +gabble_vcard_manager_set_property (GObject *object, + guint property_id, + const GValue *value, + GParamSpec *pspec) +{ + GabbleVCardManager *chan = GABBLE_VCARD_MANAGER (object); + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (chan); + + switch (property_id) { + case PROP_CONNECTION: + priv->connection = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); + break; + } +} + +static void cancel_request (GabbleVCardManagerRequest *request); + +void +gabble_vcard_manager_dispose (GObject *object) +{ + DBusGProxy *bus_proxy; + GabbleVCardManager *self = GABBLE_VCARD_MANAGER (object); + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (self); + bus_proxy = tp_get_bus_proxy (); + + if (priv->dispose_has_run) + return; + + priv->dispose_has_run = TRUE; + + /* cancel request removes the element from the list after cancelling */ + while (priv->requests) + cancel_request (priv->requests->data); + + if (G_OBJECT_CLASS (gabble_vcard_manager_parent_class)->dispose) + G_OBJECT_CLASS (gabble_vcard_manager_parent_class)->dispose (object); +} + +void +gabble_vcard_manager_finalize (GObject *object) +{ + G_OBJECT_CLASS (gabble_vcard_manager_parent_class)->finalize (object); +} + +static void +status_changed_cb (GObject *object, + guint status, + guint reason, + gpointer user_data) +{ + GabbleVCardManager *self = GABBLE_VCARD_MANAGER (user_data); + GabbleConnection *conn = GABBLE_CONNECTION (object); + + if (status == TP_CONN_STATUS_CONNECTED) + { + gchar *alias; + GabbleConnectionAliasSource alias_src; + + /* if we have a better alias, patch it into our vCard on the server */ + alias_src = _gabble_connection_get_cached_alias (conn, + conn->self_handle, + &alias); + if (alias_src > GABBLE_CONNECTION_ALIAS_FROM_VCARD) + { + /* ignore errors, just kick off the request in the background */ + gabble_vcard_manager_edit (self, 0, NULL, NULL, G_OBJECT (conn), + NULL, "NICKNAME", alias, NULL); + } + else + { + /* find out our own alias, so it's in the cache; again, + * there's nothing useful we can do with errors really + */ + gabble_vcard_manager_request (self, conn->self_handle, + 0, NULL, NULL, NULL, NULL); + } + + g_free(alias); + } +} + +/** + * gabble_vcard_manager_new: + * @conn: The #GabbleConnection to use for vCard lookup + * + * Creates an object to use for Jabber vCard lookup (JEP 0054). + * There should be one of these per connection + */ +GabbleVCardManager * +gabble_vcard_manager_new (GabbleConnection *conn) +{ + GabbleVCardManager *self; + + g_return_val_if_fail (GABBLE_IS_CONNECTION (conn), NULL); + + self = GABBLE_VCARD_MANAGER (g_object_new (GABBLE_TYPE_VCARD_MANAGER, "connection", conn, NULL)); + g_signal_connect (conn, "status-changed", + G_CALLBACK (status_changed_cb), self); + return self; +} + +static void notify_delete_request (gpointer data, GObject *obj); + +static void +delete_request (GabbleVCardManagerRequest *request) +{ + GabbleVCardManager *manager = request->manager; + GabbleVCardManagerPrivate *priv; + + gabble_debug (DEBUG_FLAG, "Discarding request %p", request); + + g_assert (NULL != request); + g_assert (GABBLE_IS_VCARD_MANAGER (manager)); + + priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (manager); + + g_assert (NULL != g_list_find (priv->requests, request)); + + priv->requests = g_list_remove (priv->requests, request); + + if (NULL != request->bound_object) + { + g_object_weak_unref (request->bound_object, notify_delete_request, request); + } + + if (0 != request->timer_id) + { + g_source_remove (request->timer_id); + } + + gabble_handle_unref (priv->connection->handles, TP_HANDLE_TYPE_CONTACT, + request->handle); + g_strfreev (request->edit_args); + + g_free (request); +} + +static gboolean +timeout_request (gpointer data) +{ + GabbleVCardManagerRequest *request = (GabbleVCardManagerRequest*) data; + GError *err; + g_return_val_if_fail (data != NULL, FALSE); + + err = g_error_new (GABBLE_VCARD_MANAGER_ERROR, GABBLE_VCARD_MANAGER_ERROR_TIMEOUT, + "Request timed out"); + gabble_debug (DEBUG_FLAG, "Request %p timed out, notifying callback %p", + request, request->callback); + if (request->callback) + { + (request->callback)(request->manager, request, request->handle, + NULL, err, request->user_data); + } + g_error_free (err); + + request->timer_id = 0; + delete_request (request); + return FALSE; +} + +static void +cancel_request (GabbleVCardManagerRequest *request) +{ + GError *err; + + g_assert (request != NULL); + + err = g_error_new (GABBLE_VCARD_MANAGER_ERROR, GABBLE_VCARD_MANAGER_ERROR_CANCELLED, + "Request cancelled"); + gabble_debug (DEBUG_FLAG, "Request %p cancelled, notifying callback %p", + request, request->callback); + if (request->callback) + { + (request->callback)(request->manager, request, request->handle, + NULL, err, request->user_data); + } + g_error_free (err); + + delete_request (request); +} + +static void +observe_vcard (GabbleConnection *conn, GabbleVCardManager *manager, + GabbleHandle handle, LmMessageNode *vcard_node) +{ + LmMessageNode *nick_node = lm_message_node_get_child (vcard_node, + "NICKNAME"); + + gabble_debug (DEBUG_FLAG, "Observing vCard for %u", handle); + NODE_DEBUG(vcard_node, "their vCard is"); + + if (nick_node) + { + const gchar *nick = lm_message_node_get_value (nick_node); + + gabble_debug (DEBUG_FLAG, "%u has \"%s\"", handle, nick ? nick : "(null)"); + + if (nick && *nick) + { + /* nicknames are comma-separated, let's use the first one */ + gchar **bits = g_strsplit (nick, ",", 2); + + if (bits[0]) + { + gchar *alias = g_strdup (bits[0]); + + gabble_debug (DEBUG_FLAG, "... using \"%s\" as their alias", alias); + + g_signal_emit (G_OBJECT (manager), signals[NICKNAME_UPDATE], + 0, handle); + if (!gabble_handle_set_qdata (conn->handles, + TP_HANDLE_TYPE_CONTACT, + handle, + gabble_vcard_manager_cache_quark(), + alias, g_free)) + { + gabble_debug (DEBUG_FLAG, "failed to cache their alias"); + g_free (alias); + } + + } + + g_strfreev (bits); + } + } + else + { + const gchar *fn = NULL; + /* let's see if they have a FN (formatted name) instead */ + nick_node = lm_message_node_get_child (vcard_node, "FN"); + if (nick_node) + fn = lm_message_node_get_value (nick_node); + gabble_debug (DEBUG_FLAG, "%u has no , but has \"%s\"", handle, + fn ? fn : "(null)"); + if (fn && *fn) + { + gchar *alias = g_strdup (fn); + + gabble_debug (DEBUG_FLAG, "... using \"%s\" as their alias", alias); + + g_signal_emit (G_OBJECT (manager), signals[NICKNAME_UPDATE], + 0, handle); + if (!gabble_handle_set_qdata (conn->handles, + TP_HANDLE_TYPE_CONTACT, + handle, + gabble_vcard_manager_cache_quark(), + alias, g_free)) + { + gabble_debug (DEBUG_FLAG, "failed to cache their alias"); + g_free (alias); + } + } + else + { + /* remember that they don't have an alias */ + if (!gabble_handle_set_qdata (conn->handles, + TP_HANDLE_TYPE_CONTACT, + handle, + gabble_vcard_manager_cache_quark (), + (gchar *) NO_ALIAS, NULL)) + gabble_debug (DEBUG_FLAG, "failed to cache their lack of vcard alias"); + } + + } +} + +static GabbleVCardManagerRequest *request_send (GabbleVCardManagerRequest *, + LmMessageNode *replacement, + const gchar *jid, + GError **); + +static LmHandlerResult +replace_reply_cb (GabbleConnection *conn, LmMessage *sent_msg, + LmMessage *reply_msg, GObject *object, gpointer user_data) +{ + LmMessageNode *vcard_node = NULL; + GError *err = NULL; + GabbleVCardManagerRequest *request = (GabbleVCardManagerRequest*) user_data; + GabbleVCardManager *manager = GABBLE_VCARD_MANAGER (object); + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (manager); + + g_assert (request); + + gabble_debug (DEBUG_FLAG, "Replace request got a reply: conn@%p, sent_msg@%p, reply_msg@%p, " + "bound object@%p, request@%p", conn, sent_msg, reply_msg, object, + user_data); + + if (!g_list_find (priv->requests, request)) + { + gabble_debug (DEBUG_FLAG, "I don't care about that request any more"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + if (lm_message_get_sub_type (reply_msg) == LM_MESSAGE_SUB_TYPE_ERROR) + { + LmMessageNode *error_node; + + error_node = lm_message_node_get_child (reply_msg->node, "error"); + if (error_node) + { + err = gabble_xmpp_error_to_g_error ( + gabble_xmpp_error_from_node (error_node)); + } + + if (err == NULL) + { + err = g_error_new (GABBLE_VCARD_MANAGER_ERROR, + GABBLE_VCARD_MANAGER_ERROR_UNKNOWN, + "an unknown error occurred"); + } + } + else + { + vcard_node = lm_message_node_get_child (sent_msg->node, "vCard"); + } + + gabble_debug (DEBUG_FLAG, "Request %p %s, notifying callback %p", request, + err ? "failed" : "succeeded", request->callback); + if (request->callback) + { + request->callback (request->manager, request, request->handle, + vcard_node, err, request->user_data); + } + delete_request (request); + + if (err) + g_error_free (err); + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +static LmHandlerResult +request_reply_cb (GabbleConnection *conn, + LmMessage *sent_msg, + LmMessage *reply_msg, + GObject *object, + gpointer user_data) +{ + LmMessageNode *vcard_node = NULL; + GError *err = NULL; + GabbleVCardManagerRequest *request = (GabbleVCardManagerRequest*) user_data; + GabbleVCardManager *manager = GABBLE_VCARD_MANAGER (object); + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (manager); + + g_assert (request); + + gabble_debug (DEBUG_FLAG, "Fetch request got a reply: conn@%p, sent_msg@%p, reply_msg@%p, " + "bound object@%p, request@%p", conn, sent_msg, reply_msg, object, + user_data); + + if (!g_list_find (priv->requests, request)) + { + gabble_debug (DEBUG_FLAG, "I don't care about that request any more"); + return LM_HANDLER_RESULT_ALLOW_MORE_HANDLERS; + } + + + if (lm_message_get_sub_type (reply_msg) == LM_MESSAGE_SUB_TYPE_ERROR) + { + LmMessageNode *error_node; + + error_node = lm_message_node_get_child (reply_msg->node, "error"); + if (error_node) + { + err = gabble_xmpp_error_to_g_error ( + gabble_xmpp_error_from_node (error_node)); + } + + if (err == NULL) + { + err = g_error_new (GABBLE_VCARD_MANAGER_ERROR, + GABBLE_VCARD_MANAGER_ERROR_UNKNOWN, + "an unknown error occurred"); + } + } + else + { + vcard_node = lm_message_node_get_child (reply_msg->node, "vCard"); + + if (NULL == vcard_node) + { + gabble_debug (DEBUG_FLAG, "successful lookup response contained no node, " + "creating an empty one"); + + vcard_node = lm_message_node_add_child (reply_msg->node, "vCard", + NULL); + lm_message_node_set_attribute (vcard_node, "xmlns", NS_VCARD_TEMP); + } + + observe_vcard (conn, manager, request->handle, vcard_node); + } + + if (vcard_node && request->edit_args) + { + gchar **ptr; + for (ptr = request->edit_args; *ptr; ptr++) + { + gchar *key = *ptr; + gchar *value = *(++ptr); + LmMessageNode *node; + + if (!value) + { + /* oops, someone passed in an odd number of args. */ + g_assert_not_reached (); + break; + } + + node = lm_message_node_get_child (vcard_node, key); + if (node) + { + lm_message_node_set_value (node, value); + } + else + { + node = lm_message_node_add_child (vcard_node, key, value); + } + } + + request_send (request, vcard_node, NULL, &err); + + if (err) + { + gabble_debug (DEBUG_FLAG, "Request %p failed, notifying callback %p", + request, request->callback); + if (request->callback) + { + request->callback (request->manager, request, request->handle, + NULL, err, request->user_data); + } + } + else + { + gabble_debug (DEBUG_FLAG, "Request %p fetch succeeded", request); + /* early return to avoid deleting the request */ + return LM_HANDLER_RESULT_REMOVE_MESSAGE; + } + } + else + { + gabble_debug (DEBUG_FLAG, "Request %p %s, notifying callback %p", + request, err ? "failed" : "succeeded", request->callback); + if (request->callback) + { + request->callback (request->manager, request, request->handle, + vcard_node, err, request->user_data); + } + } + + delete_request (request); + + if (err) + g_error_free (err); + + return LM_HANDLER_RESULT_REMOVE_MESSAGE; +} + +/* If @replacement is NULL sends a request, calling request_reply_cb when + * it returns. + * + * Otherwise steals its children and sends an update, calling + * replace_reply_cb when it returns. + * + * Frees the @request on error, returns it on success. */ +static GabbleVCardManagerRequest * +request_send (GabbleVCardManagerRequest *request, + LmMessageNode *replacement, + const gchar *jid, + GError **error) +{ + LmMessage *msg; + LmMessageNode *lm_node; + GabbleVCardManager *self = request->manager; + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (self); + + gabble_debug (DEBUG_FLAG, "Sending off request %p to %s for %s", request, + replacement ? "replace vCard" : "retrieve vCard", + jid ? jid : "myself"); + msg = lm_message_new_with_sub_type (jid, LM_MESSAGE_TYPE_IQ, + (replacement + ? LM_MESSAGE_SUB_TYPE_SET + : LM_MESSAGE_SUB_TYPE_GET)); + lm_node = lm_message_node_add_child (msg->node, "vCard", NULL); + lm_message_node_set_attribute (lm_node, "xmlns", NS_VCARD_TEMP); + + if (replacement) + lm_message_node_steal_children (lm_node, replacement); + + if (! _gabble_connection_send_with_reply (priv->connection, msg, + (replacement ? replace_reply_cb : request_reply_cb), + G_OBJECT(self), request, error)) + { + delete_request (request); + lm_message_unref (msg); + return NULL; + } + else + { + if (0 == request->timer_id) + { + request->timer_id = + g_timeout_add (request->timeout, timeout_request, request); + } + lm_message_unref (msg); + return request; + } +} + +static void +notify_delete_request (gpointer data, GObject *obj) +{ + GabbleVCardManagerRequest *request = (GabbleVCardManagerRequest *) data; + request->bound_object = NULL; + delete_request (request); +} + +/* Request the vCard for the given handle. When it arrives, call the given + * callback. + * + * The callback may be NULL if you just want the side-effect of this + * operation, which is to update the cached alias. + */ +GabbleVCardManagerRequest * +gabble_vcard_manager_request (GabbleVCardManager *self, + GabbleHandle handle, + guint timeout, + GabbleVCardManagerCb callback, + gpointer user_data, + GObject *object, + GError **error) +{ + GabbleVCardManagerRequest *request; + const gchar *jid; + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (self); + + if (timeout == 0) + timeout = DEFAULT_REQUEST_TIMEOUT; + + request = g_new0 (GabbleVCardManagerRequest, 1); + gabble_debug (DEBUG_FLAG, "Created request %p to retrieve <%u>'s vCard", + request, handle); + request->timeout = timeout; + request->manager = self; + gabble_handle_ref (priv->connection->handles, TP_HANDLE_TYPE_CONTACT, + handle); + request->handle = handle; + request->callback = callback; + request->user_data = user_data; + request->bound_object = object; + + if (NULL != object) + g_object_weak_ref (object, notify_delete_request, request); + + priv->requests = g_list_prepend (priv->requests, request); + if (handle == priv->connection->self_handle) + { + jid = NULL; + } + else + { + jid = gabble_handle_inspect (priv->connection->handles, + TP_HANDLE_TYPE_CONTACT, handle); + } + + return request_send (request, NULL, jid, error); +} + +GabbleVCardManagerRequest * +gabble_vcard_manager_replace (GabbleVCardManager *self, + LmMessageNode *replacement, + guint timeout, + GabbleVCardManagerCb callback, + gpointer user_data, + GObject *object, + GError **error) +{ + GabbleVCardManagerRequest *request; + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (self); + + if (timeout == 0) + timeout = DEFAULT_REQUEST_TIMEOUT; + + request = g_new0 (GabbleVCardManagerRequest, 1); + gabble_debug (DEBUG_FLAG, "Created request %p to replace my vCard", + request); + request->timeout = timeout; + request->manager = self; + gabble_handle_ref (priv->connection->handles, TP_HANDLE_TYPE_CONTACT, + priv->connection->self_handle); + request->handle = priv->connection->self_handle; + request->callback = callback; + request->user_data = user_data; + request->bound_object = object; + + if (NULL != object) + g_object_weak_ref (object, notify_delete_request, request); + + priv->requests = g_list_prepend (priv->requests, request); + + return request_send (request, replacement, NULL, error); +} + +GabbleVCardManagerRequest * +gabble_vcard_manager_edit (GabbleVCardManager *self, + guint timeout, + GabbleVCardManagerCb callback, + gpointer user_data, + GObject *object, + GError **error, + ...) +{ + va_list ap; + size_t i, argc; + GabbleVCardManagerRequest *request; + GabbleVCardManagerPrivate *priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (self); + + if (timeout == 0) + timeout = DEFAULT_REQUEST_TIMEOUT; + + request = g_new0 (GabbleVCardManagerRequest, 1); + gabble_debug (DEBUG_FLAG, "Created request %p to edit my vCard", request); + request->timeout = timeout; + request->manager = self; + gabble_handle_ref (priv->connection->handles, TP_HANDLE_TYPE_CONTACT, + priv->connection->self_handle); + request->handle = priv->connection->self_handle; + request->callback = callback; + request->user_data = user_data; + request->bound_object = object; + + if (NULL != object) + g_object_weak_ref (object, notify_delete_request, request); + + priv->requests = g_list_prepend (priv->requests, request); + + argc = 0; + va_start (ap, error); + while (va_arg (ap, const gchar *) != NULL) + { + argc++; + } + va_end (ap); + g_return_val_if_fail (argc % 2 == 0, NULL); + + request->edit_args = g_new (gchar *, argc + 1); + + va_start (ap, error); + for (i = 0; i < argc; i++) + { + request->edit_args[i] = g_strdup (va_arg (ap, const gchar *)); + } + request->edit_args[argc] = NULL; + va_end (ap); + + return request_send (request, NULL, NULL, error); +} + +void +gabble_vcard_manager_cancel_request (GabbleVCardManager *manager, + GabbleVCardManagerRequest *request) +{ + GabbleVCardManagerPrivate *priv; + + g_return_if_fail (GABBLE_IS_VCARD_MANAGER (manager)); + g_return_if_fail (NULL != request); + + priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (manager); + + g_return_if_fail (NULL != g_list_find (priv->requests, request)); + + cancel_request (request); +} + +/** + * Return the cached alias derived from the vCard for the given handle, + * if any. If there is no cached alias, return NULL. + */ +const gchar * +gabble_vcard_manager_get_cached_alias (GabbleVCardManager *manager, + GabbleHandle handle) +{ + GabbleVCardManagerPrivate *priv; + const gchar *s; + + g_return_val_if_fail (GABBLE_IS_VCARD_MANAGER (manager), NULL); + + priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (manager); + + s = gabble_handle_get_qdata (priv->connection->handles, + TP_HANDLE_TYPE_CONTACT, + handle, + gabble_vcard_manager_cache_quark()); + + if (s == NO_ALIAS) + s = NULL; + + gabble_debug (DEBUG_FLAG, "Cached alias for %u is \"%s\"", handle, s ? s : "(null)"); + return s; +} + +/** + * Return TRUE if we've tried looking up an alias for this handle before. + */ +gboolean +gabble_vcard_manager_has_cached_alias (GabbleVCardManager *manager, + GabbleHandle handle) +{ + GabbleVCardManagerPrivate *priv; + gpointer p; + + g_return_val_if_fail (GABBLE_IS_VCARD_MANAGER (manager), FALSE); + + priv = GABBLE_VCARD_MANAGER_GET_PRIVATE (manager); + p = gabble_handle_get_qdata (priv->connection->handles, + TP_HANDLE_TYPE_CONTACT, + handle, + gabble_vcard_manager_cache_quark()); + return p != NULL; +} +