From efc1abc2b131ff326c4eb6bb1a803e325394bdd7 Mon Sep 17 00:00:00 2001 From: Ferdinand Stehle Date: Fri, 19 Aug 2016 00:11:47 +0200 Subject: [PATCH 1/5] initial pep avatar fetching support --- src/capabilities.c | 2 + src/conn-avatars.c | 253 +++++++++++++++++++++++++++++++++++++++++++-- src/connection.c | 2 + src/connection.h | 4 + src/namespaces.h | 2 + 5 files changed, 252 insertions(+), 11 deletions(-) diff --git a/src/capabilities.c b/src/capabilities.c index 8afb0b354..82d66549d 100644 --- a/src/capabilities.c +++ b/src/capabilities.c @@ -54,6 +54,8 @@ static const Feature self_advertised_features[] = #endif { FEATURE_FIXED, NS_DISCO_INFO }, + { FEATURE_FIXED, NS_AVATAR_METADATA }, + { FEATURE_FIXED, NS_AVATAR_METADATA "+notify" }, { FEATURE_FIXED, NS_CHAT_STATES }, { FEATURE_FIXED, NS_NICK }, { FEATURE_FIXED, NS_NICK "+notify" }, diff --git a/src/conn-avatars.c b/src/conn-avatars.c index 681a04df4..a301e40ea 100644 --- a/src/conn-avatars.c +++ b/src/conn-avatars.c @@ -33,11 +33,19 @@ #include "namespaces.h" #include "vcard-manager.h" #include "util.h" +#include "request-pipeline.h" #define DEBUG_FLAG GABBLE_DEBUG_CONNECTION #include "debug.h" +typedef struct { + TpHandle handle; +} pep_request_ctx; + +static pep_request_ctx * +pep_avatar_request_data (GabbleConnection *conn, TpHandle handle); + /* If the SHA1 has changed, this function will copy it to self_presence, * emit a signal and push it to the server. */ static gboolean @@ -711,17 +719,27 @@ gabble_connection_request_avatars (TpSvcConnectionInterfaceAvatars *iface, if (NULL == g_hash_table_lookup (self->avatar_requests, GUINT_TO_POINTER (contact))) { - RequestAvatarsContext *ctx = g_slice_new (RequestAvatarsContext); - - ctx->conn = self; - ctx->iface = iface; - ctx->handle = contact; - - g_hash_table_insert (self->avatar_requests, - GUINT_TO_POINTER (contact), ctx); - - gabble_vcard_manager_request (self->vcard_manager, - contact, 0, request_avatars_cb, ctx, NULL); + if (g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(contact))) + { + pep_request_ctx *ctx = pep_avatar_request_data (self, contact); + + g_hash_table_insert (self->avatar_requests, + GUINT_TO_POINTER (contact), ctx); + } + else + { + RequestAvatarsContext *ctx = g_slice_new (RequestAvatarsContext); + + ctx->conn = self; + ctx->iface = iface; + ctx->handle = contact; + + g_hash_table_insert (self->avatar_requests, + GUINT_TO_POINTER (contact), ctx); + + gabble_vcard_manager_request (self->vcard_manager, + contact, 0, request_avatars_cb, ctx, NULL); + } } } } @@ -915,6 +933,8 @@ conn_avatars_fill_contact_attributes (GObject *obj, if (NULL != presence->avatar_sha1) g_value_set_string (val, presence->avatar_sha1); + else if (g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(handle))) + g_value_set_string (val, g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(handle))); else g_value_set_string (val, ""); @@ -924,6 +944,211 @@ conn_avatars_fill_contact_attributes (GObject *obj, } } +static void +pep_avatar_request_data_cb ( + GabbleConnection *conn, + WockyStanza *msg, + gpointer user_data, + GError *error) +{ + pep_request_ctx *ctx = user_data; + TpHandle handle = ctx->handle; + + WockyNode *pubsub_node, *items_node, *item_node, *data_node; + + const gchar *binval_value; + gchar *sha1; + /* todo: get mime type from metadata pep, if any */ + const gchar *mime_type = ""; + gchar *bindata; + gsize outlen; + GArray *arr; + + g_slice_free (pep_request_ctx, ctx); + + g_assert (g_hash_table_lookup (conn->avatar_requests, + GUINT_TO_POINTER (handle))); + + g_hash_table_remove (conn->avatar_requests, + GUINT_TO_POINTER (handle)); + + pubsub_node = wocky_node_get_child_ns ( + wocky_stanza_get_top_node (msg), "pubsub", NS_PUBSUB); + if (pubsub_node == NULL) + { + pubsub_node = wocky_node_get_child_ns ( + wocky_stanza_get_top_node (msg), "pubsub", NS_PUBSUB "#event"); + + if (pubsub_node == NULL) + { + STANZA_DEBUG (msg, "PEP reply with no , ignoring"); + return; + } + else + { + STANZA_DEBUG (msg, "PEP reply from buggy server with #event " + "on namespace"); + } + } + + items_node = wocky_node_get_child (pubsub_node, "items"); + if (items_node == NULL) + { + STANZA_DEBUG (msg, "No items in PEP reply"); + return; + } + + item_node = wocky_node_get_child (items_node, "item"); + if (item_node == NULL) + { + STANZA_DEBUG (msg, "No item in PEP reply"); + return; + } + + data_node = wocky_node_get_child (item_node, "data"); + if (data_node == NULL) + { + STANZA_DEBUG (msg, "No data in PEP reply"); + return; + } + + binval_value = data_node->content; + + if (NULL == binval_value) + { + g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, + "contact avatar is missing binval content"); + return; + } + + bindata = (gchar *) g_base64_decode (binval_value, &outlen); + + if (bindata == NULL) + { + g_set_error (&error, TP_ERROR, TP_ERROR_NOT_AVAILABLE, + "failed to decode avatar from base64"); + return; + } + + sha1 = sha1_hex (bindata, outlen); + arr = g_array_new (FALSE, FALSE, sizeof (gchar)); + g_array_append_vals (arr, bindata, outlen); + tp_svc_connection_interface_avatars_emit_avatar_retrieved (conn, handle, + sha1, arr, mime_type); + + g_array_unref (arr); + g_free (bindata); + + DEBUG ("retreived avatar from %d with size=%ld, sha1='%s'", handle, outlen, sha1); + + // is this really needed? + if (sha1) + { + GabblePresence *presence = gabble_presence_cache_get (conn->presence_cache, handle); + if (presence) + { + DEBUG ("presence found"); + + g_free (presence->avatar_sha1); + presence->avatar_sha1 = g_strdup (sha1); + } + else + DEBUG ("presence not found"); + } + + g_free (sha1); +} + +static void +pep_avatar_metadata_node_changed (WockyPepService *pep, + WockyBareContact *contact, + WockyStanza *stanza, + WockyNode *item, + GabbleConnection *conn) +{ + TpBaseConnection *base = (TpBaseConnection *) conn; + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + (TpBaseConnection *) conn, TP_HANDLE_TYPE_CONTACT); + TpHandle handle; + WockyNode *metadata, *info; + const gchar *jid; + const gchar *sha1; + + jid = wocky_bare_contact_get_jid (contact); + + handle = tp_handle_ensure (contact_repo, jid, NULL, NULL); + if (handle == 0) + { + DEBUG ("Invalid from: %s", jid); + return; + } + + if (NULL == item) + { + STANZA_DEBUG (stanza, "PEP event without item node, ignoring"); + return; + } + + metadata = wocky_node_get_child_ns (item, "metadata", NS_AVATAR_METADATA); + if (NULL == metadata) + { + STANZA_DEBUG (stanza, "PEP item without metadata node, ignoring"); + return; + } + + // FIXME: there may exist multiple child nodes + info = wocky_node_get_child (metadata, "info"); + if (NULL == info) + { + STANZA_DEBUG (stanza, "PEP metadata without info nodes, ignoring"); + return; + } + + sha1 = wocky_node_get_attribute (info, "id"); + + if (handle == tp_base_connection_get_self_handle (base)) + update_own_avatar_sha1 (conn, sha1, NULL); + else + tp_svc_connection_interface_avatars_emit_avatar_updated (conn, handle, sha1); + + DEBUG ("got pep avatar metadata update of '%s', sha1 = %s", jid, sha1); + + g_hash_table_insert (conn->pep_avatar_hashes, GINT_TO_POINTER(handle), g_strdup (sha1)); +} + +static pep_request_ctx * +pep_avatar_request_data (GabbleConnection *conn, TpHandle handle) +{ + TpBaseConnection *base = (TpBaseConnection *) conn; + pep_request_ctx *ctx; + WockyStanza *msg; + + const gchar *jid = NULL; + + TpHandleRepoIface *contact_repo = tp_base_connection_get_handles ( + base, TP_HANDLE_TYPE_CONTACT); + + jid = tp_handle_inspect (contact_repo, handle); + + ctx = g_slice_new0 (pep_request_ctx); + ctx->handle = handle; + + msg = wocky_stanza_build (WOCKY_STANZA_TYPE_IQ, WOCKY_STANZA_SUB_TYPE_GET, + NULL, jid, + '(', "pubsub", + ':', NS_PUBSUB, + '(', "items", + '@', "node", NS_AVATAR_DATA, + ')', + ')', + NULL); + + gabble_request_pipeline_enqueue (conn->req_pipeline, + msg, 0, pep_avatar_request_data_cb, ctx); + g_object_unref (msg); + + return ctx; +} void conn_avatars_init (GabbleConnection *conn) @@ -938,6 +1163,12 @@ conn_avatars_init (GabbleConnection *conn) tp_contacts_mixin_add_contact_attributes_iface (G_OBJECT (conn), TP_IFACE_CONNECTION_INTERFACE_AVATARS, conn_avatars_fill_contact_attributes); + + conn->pep_avatar = wocky_pep_service_new (NS_AVATAR_METADATA, TRUE); + conn->pep_avatar_hashes = g_hash_table_new (g_direct_hash, g_direct_equal); + + g_signal_connect (conn->pep_avatar, "changed", + G_CALLBACK (pep_avatar_metadata_node_changed), conn); } diff --git a/src/connection.c b/src/connection.c index 597442b48..efa1b047a 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1386,6 +1386,7 @@ gabble_connection_dispose (GObject *object) tp_clear_object (&self->pep_olpc_activities); tp_clear_object (&self->pep_olpc_current_act); tp_clear_object (&self->pep_olpc_act_props); + tp_clear_object (&self->pep_avatar); conn_sidecars_dispose (self); @@ -2036,6 +2037,7 @@ connector_connected (GabbleConnection *self, wocky_pep_service_start (self->pep_olpc_activities, self->session); wocky_pep_service_start (self->pep_olpc_current_act, self->session); wocky_pep_service_start (self->pep_olpc_act_props, self->session); + wocky_pep_service_start (self->pep_avatar, self->session); /* Don't use wocky_session_start as we don't want to start all the * components (Roster, presence-manager, etc) for now */ diff --git a/src/connection.h b/src/connection.h index 12d002353..a7d6d8af7 100644 --- a/src/connection.h +++ b/src/connection.h @@ -240,6 +240,10 @@ struct _GabbleConnection { WockyPepService *pep_olpc_activities; WockyPepService *pep_olpc_current_act; WockyPepService *pep_olpc_act_props; + WockyPepService *pep_avatar; + + /* list to recognize pep avatars */ + GHashTable *pep_avatar_hashes; /* Sidecars */ /* gchar *interface → GabbleSidecar */ diff --git a/src/namespaces.h b/src/namespaces.h index 3e9e79d02..acefddc65 100644 --- a/src/namespaces.h +++ b/src/namespaces.h @@ -28,6 +28,8 @@ #define NS_SM2 "urn:xmpp:sm:2" #define NS_SM3 "urn:xmpp:sm:3" #define NS_AMP "http://jabber.org/protocol/amp" +#define NS_AVATAR_DATA "urn:xmpp:avatar:data" +#define NS_AVATAR_METADATA "urn:xmpp:avatar:metadata" #define NS_BYTESTREAMS "http://jabber.org/protocol/bytestreams" #define NS_CHAT_STATES "http://jabber.org/protocol/chatstates" #define NS_CHAT_MARKERS "urn:xmpp:chat-markers:0" From 2f08ad3025587d25fa8e06644af12f68c8a020a0 Mon Sep 17 00:00:00 2001 From: Ferdinand Stehle Date: Thu, 18 Apr 2019 22:27:09 +0200 Subject: [PATCH 2/5] use correct format specifier for size_t --- src/conn-avatars.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conn-avatars.c b/src/conn-avatars.c index a301e40ea..3f49db8a2 100644 --- a/src/conn-avatars.c +++ b/src/conn-avatars.c @@ -1039,7 +1039,7 @@ pep_avatar_request_data_cb ( g_array_unref (arr); g_free (bindata); - DEBUG ("retreived avatar from %d with size=%ld, sha1='%s'", handle, outlen, sha1); + DEBUG ("retrieved avatar from %d with size=%zd, sha1='%s'", handle, outlen, sha1); // is this really needed? if (sha1) From bf0176aecef5ddb2064b4da16e928960f1872db7 Mon Sep 17 00:00:00 2001 From: Ferdinand Stehle Date: Sun, 21 Jun 2020 00:13:42 +0200 Subject: [PATCH 3/5] pep-avatar: select png from the list of available avatars --- src/conn-avatars.c | 43 ++++++++++++++++++++++++++++++------------- 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/src/conn-avatars.c b/src/conn-avatars.c index 3f49db8a2..de874ec3e 100644 --- a/src/conn-avatars.c +++ b/src/conn-avatars.c @@ -44,7 +44,7 @@ typedef struct { } pep_request_ctx; static pep_request_ctx * -pep_avatar_request_data (GabbleConnection *conn, TpHandle handle); +pep_avatar_request_data (GabbleConnection *conn, TpHandle handle, gchar *id); /* If the SHA1 has changed, this function will copy it to self_presence, * emit a signal and push it to the server. */ @@ -719,10 +719,10 @@ gabble_connection_request_avatars (TpSvcConnectionInterfaceAvatars *iface, if (NULL == g_hash_table_lookup (self->avatar_requests, GUINT_TO_POINTER (contact))) { - if (g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(contact))) + gchar *id; + if (id = g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(contact))) { - pep_request_ctx *ctx = pep_avatar_request_data (self, contact); - + pep_request_ctx *ctx = pep_avatar_request_data (self, contact, id); g_hash_table_insert (self->avatar_requests, GUINT_TO_POINTER (contact), ctx); } @@ -930,11 +930,12 @@ conn_avatars_fill_contact_attributes (GObject *obj, if (NULL != presence) { GValue *val = tp_g_value_slice_new (G_TYPE_STRING); + gchar *id; if (NULL != presence->avatar_sha1) g_value_set_string (val, presence->avatar_sha1); - else if (g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(handle))) - g_value_set_string (val, g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(handle))); + else if (id = g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(handle))) + g_value_set_string (val, id); else g_value_set_string (val, ""); @@ -958,8 +959,8 @@ pep_avatar_request_data_cb ( const gchar *binval_value; gchar *sha1; - /* todo: get mime type from metadata pep, if any */ - const gchar *mime_type = ""; + // we only request avatars of type png, so this is fixed for now + const gchar *mime_type = "image/png"; gchar *bindata; gsize outlen; GArray *arr; @@ -1071,6 +1072,7 @@ pep_avatar_metadata_node_changed (WockyPepService *pep, (TpBaseConnection *) conn, TP_HANDLE_TYPE_CONTACT); TpHandle handle; WockyNode *metadata, *info; + WockyNodeIter iter; const gchar *jid; const gchar *sha1; @@ -1096,11 +1098,23 @@ pep_avatar_metadata_node_changed (WockyPepService *pep, return; } - // FIXME: there may exist multiple child nodes - info = wocky_node_get_child (metadata, "info"); - if (NULL == info) + gchar *type; + info = NULL; + wocky_node_iter_init (&iter, metadata, "info", NULL); + while (wocky_node_iter_next (&iter, &info)) + { + gchar *url = wocky_node_get_attribute (info, "url"); + type = wocky_node_get_attribute (info, "type"); + //Found one of type png which is not an url node + if ((type) && (g_strcmp0(type, "image/png") == 0) && (!url)) + { + break; + } + } + + if (!info) { - STANZA_DEBUG (stanza, "PEP metadata without info nodes, ignoring"); + STANZA_DEBUG (stanza, "avatar metadata without compatible type, ignoring"); return; } @@ -1117,7 +1131,7 @@ pep_avatar_metadata_node_changed (WockyPepService *pep, } static pep_request_ctx * -pep_avatar_request_data (GabbleConnection *conn, TpHandle handle) +pep_avatar_request_data (GabbleConnection *conn, TpHandle handle, gchar *id) { TpBaseConnection *base = (TpBaseConnection *) conn; pep_request_ctx *ctx; @@ -1139,6 +1153,9 @@ pep_avatar_request_data (GabbleConnection *conn, TpHandle handle) ':', NS_PUBSUB, '(', "items", '@', "node", NS_AVATAR_DATA, + '(', "item", + '@', "id", id, + ')', ')', ')', NULL); From a70a28368eaef95211738de36997b7a45fd7cf44 Mon Sep 17 00:00:00 2001 From: Ferdinand Stehle Date: Fri, 18 Sep 2020 20:44:49 +0200 Subject: [PATCH 4/5] create pep hash table with destructors --- src/conn-avatars.c | 2 +- src/connection.c | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/conn-avatars.c b/src/conn-avatars.c index de874ec3e..14fa4a15a 100644 --- a/src/conn-avatars.c +++ b/src/conn-avatars.c @@ -1182,7 +1182,7 @@ conn_avatars_init (GabbleConnection *conn) conn_avatars_fill_contact_attributes); conn->pep_avatar = wocky_pep_service_new (NS_AVATAR_METADATA, TRUE); - conn->pep_avatar_hashes = g_hash_table_new (g_direct_hash, g_direct_equal); + conn->pep_avatar_hashes = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, g_free); g_signal_connect (conn->pep_avatar, "changed", G_CALLBACK (pep_avatar_metadata_node_changed), conn); diff --git a/src/connection.c b/src/connection.c index efa1b047a..68248e5ef 100644 --- a/src/connection.c +++ b/src/connection.c @@ -1387,6 +1387,7 @@ gabble_connection_dispose (GObject *object) tp_clear_object (&self->pep_olpc_current_act); tp_clear_object (&self->pep_olpc_act_props); tp_clear_object (&self->pep_avatar); + tp_clear_object (&self->pep_avatar_hashes); conn_sidecars_dispose (self); From 2eb766303e74d14d9e6a61b4c188c9925f7cee54 Mon Sep 17 00:00:00 2001 From: Ferdinand Stehle Date: Mon, 28 Dec 2020 21:58:46 +0100 Subject: [PATCH 5/5] fix compiler warnings --- src/conn-avatars.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/conn-avatars.c b/src/conn-avatars.c index 14fa4a15a..8293de1c0 100644 --- a/src/conn-avatars.c +++ b/src/conn-avatars.c @@ -720,7 +720,7 @@ gabble_connection_request_avatars (TpSvcConnectionInterfaceAvatars *iface, GUINT_TO_POINTER (contact))) { gchar *id; - if (id = g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(contact))) + if ((id = g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(contact)))) { pep_request_ctx *ctx = pep_avatar_request_data (self, contact, id); g_hash_table_insert (self->avatar_requests, @@ -934,7 +934,7 @@ conn_avatars_fill_contact_attributes (GObject *obj, if (NULL != presence->avatar_sha1) g_value_set_string (val, presence->avatar_sha1); - else if (id = g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(handle))) + else if ((id = g_hash_table_lookup (self->pep_avatar_hashes, GINT_TO_POINTER(handle)))) g_value_set_string (val, id); else g_value_set_string (val, ""); @@ -1098,13 +1098,12 @@ pep_avatar_metadata_node_changed (WockyPepService *pep, return; } - gchar *type; info = NULL; wocky_node_iter_init (&iter, metadata, "info", NULL); while (wocky_node_iter_next (&iter, &info)) { - gchar *url = wocky_node_get_attribute (info, "url"); - type = wocky_node_get_attribute (info, "type"); + const gchar *url = wocky_node_get_attribute (info, "url"); + const gchar *type = wocky_node_get_attribute (info, "type"); //Found one of type png which is not an url node if ((type) && (g_strcmp0(type, "image/png") == 0) && (!url)) {