From 2581587a83618503f6a1798e72526fb1b96baca4 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 5 Oct 2012 17:54:04 +0100 Subject: [PATCH 2/2] Track Avatars using McdAccount, and use the self-contact for it --- src/mcd-account.c | 234 +++++++++++++++++++++++++- src/mcd-connection.c | 271 ------------------------------- tests/twisted/account-manager/avatar.py | 1 - 3 files changed, 229 insertions(+), 277 deletions(-) diff --git a/src/mcd-account.c b/src/mcd-account.c index 2f3051e..3504de2 100644 --- a/src/mcd-account.c +++ b/src/mcd-account.c @@ -173,6 +173,7 @@ struct _McdAccountPrivate gboolean removed; gboolean always_on; gboolean changing_presence; + gboolean setting_avatar; gboolean hidden; gboolean always_dispatch; @@ -1378,6 +1379,163 @@ get_nickname (TpSvcDBusProperties *self, const gchar *name, GValue *value) mcd_account_get_string_val (account, name, value); } +static void +mcd_account_self_contact_notify_avatar_file_cb (McdAccount *self, + GParamSpec *unused_param_spec G_GNUC_UNUSED, + TpContact *self_contact) +{ + const gchar *token = tp_contact_get_avatar_token (self_contact); + gchar *prev_token; + GFile *file = tp_contact_get_avatar_file (self_contact); + GError *error = NULL; + gboolean changed; + + if (self->priv->setting_avatar) + { + DEBUG ("Ignoring avatar change notification: we are setting ours"); + return; + } + + prev_token = _mcd_account_get_avatar_token (self); + changed = tp_strdiff (prev_token, token); + g_free (prev_token); + + if (!changed) + { + DEBUG ("Avatar unchanged: '%s'", token); + return; + } + + if (file == NULL) + { + if (!_mcd_account_set_avatar (self, NULL, "", "", &error)) + { + DEBUG ("Attempt to clear avatar failed: %s", error->message); + g_clear_error (&error); + } + } + else + { + gchar *contents = NULL; + gsize len = 0; + GArray *arr; + + if (!g_file_load_contents (file, NULL, &contents, &len, NULL, &error)) + { + gchar *uri = g_file_get_uri (file); + + WARNING ("Unable to read avatar file %s: %s", uri, error->message); + g_clear_error (&error); + g_free (uri); + return; + } + + if (G_UNLIKELY (len > G_MAXUINT)) + { + gchar *uri = g_file_get_uri (file); + + WARNING ("Avatar file %s was ludicrously huge", uri); + g_free (uri); + g_free (contents); + return; + } + + arr = g_array_sized_new (TRUE, FALSE, 1, (guint) len); + g_array_append_vals (arr, contents, (guint) len); + g_free (contents); + + if (!_mcd_account_set_avatar (self, arr, + tp_contact_get_avatar_mime_type (self_contact), + tp_contact_get_avatar_token (self_contact), &error)) + { + DEBUG ("Attempt to save avatar failed: %s", error->message); + g_clear_error (&error); + } + + g_array_unref (arr); + } +} + +static void +avatars_set_avatar_cb (TpConnection *tp_connection, + const gchar *token, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + McdAccount *self = MCD_ACCOUNT (weak_object); + + self->priv->setting_avatar = FALSE; + + if (error != NULL) + { + WARNING ("%s: %s", self->priv->unique_name, error->message); + } + else + { + DEBUG ("%s: new token %s", self->priv->unique_name, token); + _mcd_account_set_avatar_token (self, token); + } +} + +static void +avatars_clear_avatar_cb (TpConnection *tp_connection, + const GError *error, + gpointer user_data, + GObject *weak_object) +{ + McdAccount *self = MCD_ACCOUNT (weak_object); + + self->priv->setting_avatar = FALSE; + + if (error != NULL) + { + WARNING ("%s: %s", self->priv->unique_name, error->message); + } + else + { + DEBUG ("%s: success", self->priv->unique_name); + _mcd_account_set_avatar_token (self, ""); + } +} + +static void +mcd_account_send_avatar_to_connection (McdAccount *self, + const GArray *avatar, + const gchar *mime_type) +{ + if (self->priv->tp_connection == NULL) + return; + + if (self->priv->self_contact == NULL) + return; + + DEBUG ("%s: %u bytes", self->priv->unique_name, avatar->len); + + if (tp_proxy_has_interface_by_id (self->priv->tp_connection, + TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS)) + { + self->priv->setting_avatar = TRUE; + + if (avatar->len > 0 && avatar->len < G_MAXUINT) + { + tp_cli_connection_interface_avatars_call_set_avatar ( + self->priv->tp_connection, -1, avatar, mime_type, + avatars_set_avatar_cb, NULL, NULL, (GObject *) self); + } + else + { + tp_cli_connection_interface_avatars_call_clear_avatar ( + self->priv->tp_connection, -1, avatars_clear_avatar_cb, + NULL, NULL, (GObject *) self); + } + } + else + { + DEBUG ("unsupported, ignoring"); + } +} + static gboolean set_avatar (TpSvcDBusProperties *self, const gchar *name, const GValue *value, GError **error) @@ -3953,11 +4111,7 @@ _mcd_account_set_avatar (McdAccount *account, const GArray *avatar, MC_ACCOUNTS_KEY_AVATAR_TOKEN, NULL, FALSE); - /* this is a no-op if the connection doesn't support avatars */ - if (priv->connection != NULL) - { - _mcd_connection_set_avatar (priv->connection, avatar, mime_type); - } + mcd_account_send_avatar_to_connection (account, avatar, mime_type); } mcd_storage_commit (priv->storage, account_name); @@ -4522,6 +4676,67 @@ _mcd_account_get_old_avatar_filename (McdAccount *account, } static void +mcd_account_process_initial_avatar_token (McdAccount *self, + TpContact *self_contact) +{ + gchar *prev_token = _mcd_account_get_avatar_token (self); + const gchar *token = tp_contact_get_avatar_token (self_contact); + + /* If we have a stored avatar but no avatar token, we must have + * changed it locally; set it. + * + * Meanwhile, if the self-contact's avatar token is empty, this is a + * protocol like link-local XMPP where avatars don't persist. + * Upload ours, if any. */ + if (tp_str_empty (prev_token) || tp_str_empty (token)) + { + GArray *avatar = NULL; + gchar *mime_type = NULL; + + _mcd_account_get_avatar (self, &avatar, &mime_type); + + if (avatar != NULL) + { + if (tp_str_empty (prev_token)) + DEBUG ("We have an avatar that has never been uploaded"); + if (tp_str_empty (token)) + DEBUG ("We have an avatar and the server doesn't"); + + mcd_account_send_avatar_to_connection (self, avatar, mime_type); + g_array_unref (avatar); + g_free (mime_type); + return; + } + + tp_clear_pointer (&avatar, g_array_unref); + g_free (mime_type); + } + + /* Otherwise, if the self-contact's avatar token + * differs from ours, one of our "other selves" must have changed + * it remotely. Behave the same as if it changes remotely + * mid-session - i.e. download it and use it as our new avatar. */ + if (tp_strdiff (token, prev_token)) + { + GFile *file = tp_contact_get_avatar_file (self_contact); + + DEBUG ("The server's avatar does not match ours"); + + if (file != NULL) + { + /* We have already downloaded it: copy it. */ + mcd_account_self_contact_notify_avatar_file_cb (self, NULL, + self->priv->self_contact); + } + /* ... else we haven't downloaded it yet, but when we do, + * notify::avatar-file will go off. */ + } + + g_free (prev_token); +} + + +static void mcd_account_self_contact_upgraded_cb (GObject *source_object, GAsyncResult *res, gpointer user_data) @@ -4545,10 +4760,17 @@ mcd_account_self_contact_upgraded_cb (GObject *source_object, if (self_contact == self->priv->self_contact) { + DEBUG ("%s", tp_contact_get_identifier (self_contact)); + tp_g_signal_connect_object (self_contact, "notify::alias", G_CALLBACK (mcd_account_self_contact_notify_alias_cb), self, G_CONNECT_SWAPPED); mcd_account_self_contact_notify_alias_cb (self, NULL, self_contact); + + tp_g_signal_connect_object (self_contact, "notify::avatar-file", + G_CALLBACK (mcd_account_self_contact_notify_avatar_file_cb), + self, G_CONNECT_SWAPPED); + mcd_account_process_initial_avatar_token (self, self_contact); } else { @@ -4575,6 +4797,8 @@ mcd_account_self_contact_changed_cb (McdAccount *self, TpConnection *tp_connection) { static const TpContactFeature contact_features[] = { + TP_CONTACT_FEATURE_AVATAR_TOKEN, + TP_CONTACT_FEATURE_AVATAR_DATA, TP_CONTACT_FEATURE_ALIAS }; TpContact *self_contact; diff --git a/src/mcd-connection.c b/src/mcd-connection.c index 68ea8c8..492dd56 100644 --- a/src/mcd-connection.c +++ b/src/mcd-connection.c @@ -102,9 +102,7 @@ struct _McdConnectionPrivate TpConnectionStatusReason abort_reason; guint got_contact_capabilities : 1; - guint setting_avatar : 1; guint has_presence_if : 1; - guint has_avatars_if : 1; guint has_capabilities_if : 1; guint has_contact_capabilities_if : 1; guint has_power_saving_if : 1; @@ -585,270 +583,6 @@ _foreach_channel_remove (McdMission * mission, McdOperation * operation) } static void -avatars_set_avatar_cb (TpConnection *proxy, const gchar *token, - const GError *error, gpointer user_data, - GObject *weak_object) -{ - McdConnectionPrivate *priv = user_data; - - priv->setting_avatar = FALSE; - if (error) - { - g_warning ("%s: error: %s", G_STRFUNC, error->message); - return; - } - DEBUG ("received token: %s", token); - _mcd_account_set_avatar_token (priv->account, token); -} - -static void -avatars_clear_avatar_cb (TpConnection *proxy, const GError *error, - gpointer user_data, GObject *weak_object) -{ - if (!error) - { - DEBUG ("Clear avatar succeeded"); - } - else - { - g_warning ("%s: error: %s", G_STRFUNC, error->message); - } -} - -/* this signal handler only deals with our own avatar updates */ -static void -on_avatar_retrieved (TpConnection *proxy, guint contact_id, const gchar *token, - const GArray *avatar, const gchar *mime_type, - gpointer user_data, GObject *weak_object) -{ - McdConnectionPrivate *priv = user_data; - gchar *prev_token = NULL; - - if (contact_id != tp_connection_get_self_handle (proxy)) return; - - /* if we are setting the avatar, we must ignore this signal */ - if (priv->setting_avatar) return; - - DEBUG ("Avatar retrieved for contact %d, token: %s", contact_id, token); - prev_token = _mcd_account_get_avatar_token (priv->account); - - if (!prev_token || strcmp (token, prev_token) != 0) - { - DEBUG ("received mime-type: %s", mime_type); - _mcd_account_set_avatar (priv->account, avatar, mime_type, token, - NULL); - } - g_free (prev_token); -} - -static void -avatars_request_avatars_cb (TpConnection *proxy, const GError *error, - gpointer user_data, GObject *weak_object) -{ - if (error) - { - g_warning ("%s: error: %s", G_STRFUNC, error->message); - } -} - -static void -avatars_known_token_cb (TpConnection *proxy, GHashTable *tokens, - const GError *error, gpointer user_data, - GObject *weak_object) -{ - McdConnection *connection = MCD_CONNECTION (weak_object); - McdConnectionPrivate *priv = connection->priv; - const gchar *token; - TpHandle self_handle = tp_connection_get_self_handle (proxy); - gpointer handle = user_data; - - if (error) - { - g_warning ("%s: error: %s", G_STRFUNC, error->message); - return; - } - - if (handle != GUINT_TO_POINTER (self_handle)) - return; - - token = g_hash_table_lookup (tokens, handle); - - /* we have a token and it is not the empty string (ie no avatar) */ - if (token != NULL && *token != '\0') - { - GArray handles = { (gchar *) &handle, 1 }; - - /* if we have an avatar, set off the request to fetch it: this will * - get picked up as we connect to the retrieved signal elsewhere */ - tp_cli_connection_interface_avatars_call_request_avatars (priv->tp_conn, - -1, - &handles, - avatars_request_avatars_cb, - NULL, - NULL, - weak_object); - } - else - { /* no avatar => will never get a retrieved signal - remove manually */ - GError *avatar_error = NULL; - McdAccount *account = mcd_connection_get_account (connection); - - if (!_mcd_account_set_avatar (account, NULL, "", "", &avatar_error)) - DEBUG ("Attempt to clear avatar failed: %s", avatar_error->message); - } -} - -/* this signal handler only deals with our own avatar updates */ -static void -on_avatar_updated (TpConnection *proxy, guint contact_id, const gchar *token, - gpointer user_data, GObject *weak_object) -{ - McdConnectionPrivate *priv = user_data; - McdConnection *connection = MCD_CONNECTION (weak_object); - gchar *prev_token; - - if (contact_id != tp_connection_get_self_handle (proxy)) return; - - /* if we are setting the avatar, we must ignore this signal */ - if (priv->setting_avatar) return; - - DEBUG ("contact %d, token: %s", contact_id, token); - prev_token = _mcd_account_get_avatar_token (priv->account); - - if (!prev_token || strcmp (token, prev_token) != 0) - { - GArray handles = { (gchar *) &contact_id, 1 }; - - DEBUG ("avatar has changed or been erased"); - tp_cli_connection_interface_avatars_call_get_known_avatar_tokens (priv->tp_conn, - -1, - &handles, - avatars_known_token_cb, - GUINT_TO_POINTER (contact_id), - NULL, - (GObject *)connection); - } - g_free (prev_token); -} - -void -_mcd_connection_set_avatar (McdConnection *connection, - const GArray *avatar, - const gchar *mime_type) -{ - McdConnectionPrivate *priv = connection->priv; - - if (!priv->has_avatars_if) - return; - - DEBUG ("called"); - if (avatar->len > 0 && avatar->len < G_MAXUINT) - { - tp_cli_connection_interface_avatars_call_set_avatar (priv->tp_conn, -1, - avatar, mime_type, - avatars_set_avatar_cb, - priv, NULL, - (GObject *)connection); - priv->setting_avatar = TRUE; - } - else - tp_cli_connection_interface_avatars_call_clear_avatar (priv->tp_conn, -1, - avatars_clear_avatar_cb, - NULL, - g_free, - (GObject *)connection); -} - -static void -avatars_request_tokens_cb (TpConnection *proxy, GHashTable *tokens, - const GError *error, gpointer user_data, - GObject *weak_object) -{ - McdConnectionPrivate *priv = (McdConnectionPrivate *)user_data; - McdConnection *connection = MCD_CONNECTION (weak_object); - GArray *avatar = NULL; - const gchar *token; - gchar *mime_type; - TpHandle self_handle; - - if (error) - { - g_warning ("%s: error: %s", G_STRFUNC, error->message); - return; - } - - self_handle = tp_connection_get_self_handle (proxy); - token = g_hash_table_lookup (tokens, GUINT_TO_POINTER (self_handle)); - - if (token) - { - /* act as though the avatar had changed to this */ - on_avatar_updated (proxy, self_handle, token, priv, weak_object); - return; - } - - _mcd_account_get_avatar (priv->account, &avatar, &mime_type); - if (avatar) - { - DEBUG ("No avatar set, setting our own"); - _mcd_connection_set_avatar (connection, avatar, mime_type); - g_array_unref (avatar); - } - g_free (mime_type); -} - -static void -_mcd_connection_setup_avatar (McdConnection *connection) -{ - McdConnectionPrivate *priv = connection->priv; - gchar *mime_type, *token; - GArray *avatar; - - if (!priv->has_avatars_if) - return; - - tp_cli_connection_interface_avatars_connect_to_avatar_updated (priv->tp_conn, - on_avatar_updated, - priv, NULL, - (GObject *)connection, - NULL); - tp_cli_connection_interface_avatars_connect_to_avatar_retrieved (priv->tp_conn, - on_avatar_retrieved, - priv, NULL, - (GObject *)connection, - NULL); - priv->setting_avatar = FALSE; - - _mcd_account_get_avatar (priv->account, &avatar, &mime_type); - - if (avatar) - { - token = _mcd_account_get_avatar_token (priv->account); - g_free (token); - if (!token) - _mcd_connection_set_avatar (connection, avatar, mime_type); - else - { - GArray handles; - TpHandle self_handle; - - DEBUG ("checking for server token"); - /* Set the avatar only if no other one was set */ - self_handle = tp_connection_get_self_handle (priv->tp_conn); - handles.len = 1; - handles.data = (gchar *)&self_handle; - tp_cli_connection_interface_avatars_call_get_known_avatar_tokens (priv->tp_conn, -1, - &handles, - avatars_request_tokens_cb, - priv, NULL, - (GObject *)connection); - } - g_array_unref (avatar); - } - g_free (mime_type); -} - -static void _mcd_connection_setup_power_saving (McdConnection *connection) { McdConnectionPrivate *priv = connection->priv; @@ -1408,8 +1142,6 @@ on_connection_ready (GObject *source_object, GAsyncResult *result, priv->has_presence_if = tp_proxy_has_interface_by_id (tp_conn, TP_IFACE_QUARK_CONNECTION_INTERFACE_SIMPLE_PRESENCE); - priv->has_avatars_if = tp_proxy_has_interface_by_id (tp_conn, - TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS); priv->has_capabilities_if = tp_proxy_has_interface_by_id (tp_conn, TP_IFACE_QUARK_CONNECTION_INTERFACE_CAPABILITIES); priv->has_contact_capabilities_if = tp_proxy_has_interface_by_id (tp_conn, @@ -1420,9 +1152,6 @@ on_connection_ready (GObject *source_object, GAsyncResult *result, if (priv->has_presence_if) _mcd_connection_setup_presence (connection); - if (priv->has_avatars_if) - _mcd_connection_setup_avatar (connection); - if (priv->has_power_saving_if) _mcd_connection_setup_power_saving (connection); diff --git a/tests/twisted/account-manager/avatar.py b/tests/twisted/account-manager/avatar.py index af89c6f..dbeb8c6 100644 --- a/tests/twisted/account-manager/avatar.py +++ b/tests/twisted/account-manager/avatar.py @@ -118,7 +118,6 @@ def test(q, bus, mc): conn.forget_avatar() q.dbus_emit(conn.object_path, cs.CONN_IFACE_AVATARS, 'AvatarUpdated', conn.self_handle, '', signature='us') - q.expect('dbus-method-call', method='GetKnownAvatarTokens') q.expect('dbus-signal', path=account.object_path, interface=cs.ACCOUNT_IFACE_AVATAR, signal='AvatarChanged') -- 1.7.10.4