summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorXavier Claessens <xavier.claessens@collabora.co.uk>2012-06-09 07:49:36 +0200
committerXavier Claessens <xavier.claessens@collabora.co.uk>2012-09-13 10:54:40 +0200
commit37c2ff7347415907ef4025643ba9e7b51aac240c (patch)
tree7785ec7e34aa5c4acca3a3554974ba7336a88b3c
parent0acfcbd3b79a15a6f3004ce3790f372246353f84 (diff)
Add TpAvatarsMixin
This is a helper to implement the new Avatars iface with CM-side caching
-rw-r--r--docs/reference/telepathy-glib-docs.sgml1
-rw-r--r--docs/reference/telepathy-glib-sections.txt21
-rw-r--r--telepathy-glib/Makefile.am2
-rw-r--r--telepathy-glib/avatars-mixin.c878
-rw-r--r--telepathy-glib/avatars-mixin.h81
-rw-r--r--telepathy-glib/base-connection-internal.h6
-rw-r--r--telepathy-glib/base-connection.c24
-rw-r--r--telepathy-glib/connection-internal.h2
-rw-r--r--telepathy-glib/connection.c12
-rw-r--r--telepathy-glib/telepathy-glib.h1
10 files changed, 1014 insertions, 14 deletions
diff --git a/docs/reference/telepathy-glib-docs.sgml b/docs/reference/telepathy-glib-docs.sgml
index 5e75db4e9..0dbed5b6c 100644
--- a/docs/reference/telepathy-glib-docs.sgml
+++ b/docs/reference/telepathy-glib-docs.sgml
@@ -75,6 +75,7 @@
<xi:include href="xml/channel-manager.xml"/>
<xi:include href="xml/base-contact-list.xml"/>
<xi:include href="xml/contacts-mixin.xml"/>
+ <xi:include href="xml/avatars-mixin.xml"/>
<xi:include href="xml/dbus-properties-mixin.xml"/>
<xi:include href="xml/exportable-channel.xml"/>
<xi:include href="xml/base-channel.xml"/>
diff --git a/docs/reference/telepathy-glib-sections.txt b/docs/reference/telepathy-glib-sections.txt
index 1a549229e..4267e5373 100644
--- a/docs/reference/telepathy-glib-sections.txt
+++ b/docs/reference/telepathy-glib-sections.txt
@@ -1701,6 +1701,27 @@ TP_CONTACTS_MIXIN
<SECTION>
<INCLUDE>telepathy-glib/telepathy-glib.h</INCLUDE>
+<FILE>avatars-mixin</FILE>
+TpAvatarsMixin
+TpAvatarsMixinClearAvatarFunc
+TpAvatarsMixinRequestAvatarsFunc
+TpAvatarsMixinSetAvatarFunc
+tp_avatars_mixin_avatar_changed
+tp_avatars_mixin_avatar_retrieved
+tp_avatars_mixin_drop_avatar
+tp_avatars_mixin_finalize
+tp_avatars_mixin_iface_init
+tp_avatars_mixin_init
+tp_avatars_mixin_init_dbus_properties
+tp_avatars_mixin_register_with_contacts_mixin
+<SUBSECTION Private>
+TpAvatarsMixinPrivate
+<SUBSECTION Standard>
+TP_AVATARS_MIXIN
+</SECTION>
+
+<SECTION>
+<INCLUDE>telepathy-glib/telepathy-glib.h</INCLUDE>
<FILE>presence-mixin</FILE>
TpPresenceStatusOptionalArgumentSpec
TpPresenceStatusSpec
diff --git a/telepathy-glib/Makefile.am b/telepathy-glib/Makefile.am
index d43ab4c36..00e7bebc5 100644
--- a/telepathy-glib/Makefile.am
+++ b/telepathy-glib/Makefile.am
@@ -46,6 +46,7 @@ tpginclude_HEADERS = \
account-request.h \
automatic-client-factory.h \
add-dispatch-operation-context.h \
+ avatars-mixin.h \
base-call-channel.h \
base-call-content.h \
base-call-stream.h \
@@ -219,6 +220,7 @@ libtelepathy_glib_main_internal_la_SOURCES = \
automatic-client-factory.c \
add-dispatch-operation-context-internal.h \
add-dispatch-operation-context.c \
+ avatars-mixin.c \
base-call-channel.c \
base-call-content.c \
base-call-stream.c \
diff --git a/telepathy-glib/avatars-mixin.c b/telepathy-glib/avatars-mixin.c
new file mode 100644
index 000000000..bcc4bf65f
--- /dev/null
+++ b/telepathy-glib/avatars-mixin.c
@@ -0,0 +1,878 @@
+/*
+ * avatars-mixin.c - Source for TpAvatarsMixin
+ * Copyright (C) 2012 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:avatars-mixin
+ * @title: TpAvatarsMixin
+ * @short_description: a mixin implementation of the Avatars connection
+ * interface
+ *
+ * This mixin can be added to a #TpBaseConnection subclass to implement the
+ * Avatars interface.
+ *
+ * To use the avatars mixin, include a #TpAvatarsMixin somewhere in your
+ * instance structure, and call tp_avatars_mixin_init() from your init function
+ * or constructor, and tp_avatars_mixin_finalize() from your finalize function.
+ *
+ * <section>
+ * <title>Implementing Avatars</title>
+ * <para>
+ * You can implement #TpSvcConnectionInterfaceAvatars as follows:
+ * <itemizedlist>
+ * <listitem>
+ * <para>use the #TpContactsMixin and
+ * <link linkend="telepathy-glib-dbus-properties-mixin">TpDBusPropertiesMixin</link>;</para>
+ * </listitem>
+ * <listitem>
+ * <para>pass tp_avatars_mixin_iface_init() as an
+ * argument to G_IMPLEMENT_INTERFACE(), like so:
+ * </para>
+ * |[
+ * G_DEFINE_TYPE_WITH_CODE (MyConnection, my_connection,
+ * TP_TYPE_BASE_CONNECTION,
+ * // ...
+ * G_IMPLEMENT_INTERFACE (TP_TYPE_SVC_CONNECTION_INTERFACE_AVATARS,
+ * tp_avatars_mixin_iface_init);
+ * // ...
+ * )
+ * ]|
+ * </listitem>
+ * <listitem>
+ * <para>
+ * call tp_avatars_mixin_init_dbus_properties() in the
+ * #GTypeInfo class_init function;
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * call tp_avatars_mixin_register_with_contacts_mixin()
+ * in the #GObjectClass constructed function.
+ * </para>
+ * </listitem>
+ * </itemizedlist>
+ * </para>
+ * </section>
+ *
+ * Since: 0.UNRELEASED
+ */
+
+/**
+ * TpAvatarsMixinSetAvatarFunc:
+ * @object: An instance of a #TpBaseConnection subclass implementing the avatars
+ * interface with this mixin
+ * @avatar: An array containing the avatar data to set
+ * @mime_type: The MIME Type of @avatar is known
+ * @error: Used to return a Telepathy D-Bus error if %FALSE is returned
+ *
+ * Signature of a callback to be used to set the user's avatar.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ * Since: 0.UNRELEASED
+ */
+
+/**
+ * TpAvatarsMixinClearAvatarFunc:
+ * @object: An instance of a #TpBaseConnection subclass implementing the avatars
+ * interface with this mixin
+ * @error: Used to return a Telepathy D-Bus error if %FALSE is returned
+ *
+ * Signature of a callback to be used to clear the user's avatar.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ * Since: 0.UNRELEASED
+ */
+
+/**
+ * TpAvatarsMixinRequestAvatarsFunc:
+ * @object: An instance of a #TpBaseConnection subclass implementing the avatars
+ * interface with this mixin
+ * @contacts: An array of #TpHandle for the contacts to request avatars for
+ * @error: Used to return a Telepathy D-Bus error if %FALSE is returned
+ *
+ * Signature of a callback to be used to start avatar request for the given
+ * contacts.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ * Since: 0.UNRELEASED
+ */
+
+/**
+ * TpAvatarsMixin:
+ *
+ * Structure to be included in the instance structure of objects that
+ * use this mixin. Initialize it with tp_avatars_mixin_init().
+ *
+ * There are no public fields.
+ * Since: 0.UNRELEASED
+ */
+
+#include "config.h"
+
+#include <telepathy-glib/avatars-mixin.h>
+
+#include <dbus/dbus-glib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <telepathy-glib/base-connection.h>
+#include <telepathy-glib/dbus.h>
+#include <telepathy-glib/dbus-properties-mixin.h>
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/errors.h>
+#include <telepathy-glib/gtypes.h>
+#include <telepathy-glib/interfaces.h>
+#include <telepathy-glib/connection-internal.h>
+#include <telepathy-glib/contacts-mixin.h>
+#include <telepathy-glib/svc-connection.h>
+
+#define DEBUG_FLAG TP_DEBUG_CONTACTS
+#include "debug-internal.h"
+#include "base-connection-internal.h"
+
+/* TYPE MACROS */
+#define TP_AVATARS_MIXIN_OFFSET_QUARK (tp_avatars_mixin_get_offset_quark ())
+#define TP_AVATARS_MIXIN_OFFSET(o) \
+ tp_mixin_instance_get_offset (o, TP_AVATARS_MIXIN_OFFSET_QUARK)
+#define TP_AVATARS_MIXIN(o) \
+ ((TpAvatarsMixin *) tp_mixin_offset_cast (o, TP_AVATARS_MIXIN_OFFSET (o)))
+
+struct _TpAvatarsMixinPrivate
+{
+ /* Virtual functions */
+ TpAvatarsMixinSetAvatarFunc set_avatar;
+ TpAvatarsMixinClearAvatarFunc clear_avatar;
+ TpAvatarsMixinRequestAvatarsFunc request_avatars;
+
+ /* Immutable properties */
+ gboolean avatar_persists;
+ TpAvatarRequirements *requirements;
+
+ /* TpHandle -> owned AvatarData
+ * Contacts whose avatar is known and cached. If the contact is known to have
+ * no avatar, value is NULL. */
+ GHashTable *avatars;
+
+ /* This is the set of contacts whose avatar needs to be requested but no
+ * client is currently interested. This is used to request them all once a
+ * client claims interest. */
+ TpIntset *needs_request;
+};
+
+typedef struct
+{
+ gchar *token;
+ gchar *uri;
+} AvatarData;
+
+static AvatarData *
+avatar_data_new (const gchar *token,
+ GFile *file)
+{
+ AvatarData *a;
+
+ a = g_slice_new0 (AvatarData);
+
+ a->token = g_strdup (token);
+ a->uri = g_file_get_uri (file);
+
+ return a;
+}
+
+static void
+avatar_data_free (AvatarData *a)
+{
+ if (a != NULL)
+ {
+ g_free (a->token);
+ g_free (a->uri);
+ g_slice_free (AvatarData, a);
+ }
+}
+
+static GQuark
+tp_avatars_mixin_get_offset_quark (void)
+{
+ static GQuark offset_quark = 0;
+ if (!offset_quark)
+ offset_quark = g_quark_from_static_string ("TpAvatarsMixinOffsetQuark");
+ return offset_quark;
+}
+
+static void
+clients_interested_cb (TpBaseConnection *connection,
+ gchar *token,
+ gpointer user_data)
+{
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (connection);
+ GArray *handles;
+
+ DEBUG ("A client is now interested in avatars");
+
+ if (tp_intset_is_empty (self->priv->needs_request))
+ return;
+
+ handles = tp_intset_to_array (self->priv->needs_request);
+ self->priv->request_avatars ((GObject *) connection, handles, NULL);
+
+ g_array_unref (handles);
+ tp_intset_clear (self->priv->needs_request);
+}
+
+/**
+ * tp_avatars_mixin_init: (skip)
+ * @object: An instance of the implementation that uses this mixin
+ * @offset: The byte offset of the TpAvatarsMixin within the object structure
+ * @set_avatar: a #TpAvatarsMixinSetAvatarFunc
+ * @clear_avatar: a #TpAvatarsMixinClearAvatarFunc
+ * @request_avatars: a #TpAvatarsMixinRequestAvatarsFunc
+ * @avatar_persists: whether or not user's avatar is stored on server
+ * @requirements: a #TpAvatarRequirements
+ *
+ * Initialize the avatars mixin. Should be called from the implementation's
+ * instance init function like so:
+ *
+ * <informalexample><programlisting>
+ * tp_avatars_mixin_init ((GObject *) self,
+ * G_STRUCT_OFFSET (SomeObject, avatars_mixin),
+ * _set_avatar, _clear_avatar, _request_avatars,
+ * TRUE, requirements);
+ * </programlisting></informalexample>
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_avatars_mixin_init (GObject *object,
+ glong offset,
+ TpAvatarsMixinSetAvatarFunc set_avatar,
+ TpAvatarsMixinClearAvatarFunc clear_avatar,
+ TpAvatarsMixinRequestAvatarsFunc request_avatars,
+ gboolean avatar_persists,
+ TpAvatarRequirements *requirements)
+{
+ TpBaseConnection *base = (TpBaseConnection *) object;
+ TpAvatarsMixin *self;
+
+ g_return_if_fail (G_IS_OBJECT (object));
+ g_return_if_fail (set_avatar != NULL);
+ g_return_if_fail (clear_avatar != NULL);
+ g_return_if_fail (request_avatars != NULL);
+ g_return_if_fail (requirements != NULL);
+
+ g_type_set_qdata (G_OBJECT_TYPE (object),
+ TP_AVATARS_MIXIN_OFFSET_QUARK,
+ GINT_TO_POINTER (offset));
+
+ self = TP_AVATARS_MIXIN (object);
+ self->priv = g_slice_new0 (TpAvatarsMixinPrivate);
+
+ self->priv->set_avatar = set_avatar;
+ self->priv->clear_avatar = clear_avatar;
+ self->priv->request_avatars = request_avatars;
+
+ self->priv->avatar_persists = avatar_persists;
+ self->priv->requirements = tp_avatar_requirements_copy (requirements);
+
+ self->priv->avatars = g_hash_table_new_full (NULL, NULL,
+ NULL, (GDestroyNotify) avatar_data_free);
+ self->priv->needs_request = tp_intset_new ();
+
+ tp_base_connection_add_possible_client_interest (base,
+ TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS);
+ g_signal_connect (object,
+ "clients-interested::" TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ G_CALLBACK (clients_interested_cb), NULL);
+}
+
+/**
+ * tp_avatars_mixin_finalize: (skip)
+ * @object: An object with this mixin.
+ *
+ * Free resources held by the avatars mixin.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_avatars_mixin_finalize (GObject *object)
+{
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+
+ tp_avatar_requirements_destroy (self->priv->requirements);
+ g_hash_table_unref (self->priv->avatars);
+ tp_intset_destroy (self->priv->needs_request);
+
+ g_slice_free (TpAvatarsMixinPrivate, self->priv);
+}
+
+enum {
+ MIXIN_DP_AVATAR_PERSISTS,
+ MIXIN_DP_SUPPORTED_AVATAR_MIME_TYPES,
+ MIXIN_DP_MINIMUM_AVATAR_HEIGHT,
+ MIXIN_DP_MINIMUM_AVATAR_WIDTH,
+ MIXIN_DP_RECOMMENDED_AVATAR_HEIGHT,
+ MIXIN_DP_RECOMMENDED_AVATAR_WIDTH,
+ MIXIN_DP_MAXIMUM_AVATAR_HEIGHT,
+ MIXIN_DP_MAXIMUM_AVATAR_WIDTH,
+ MIXIN_DP_MAXIMUM_AVATAR_BYTES,
+ NUM_MIXIN_DBUS_PROPERTIES
+};
+
+static TpDBusPropertiesMixinPropImpl known_avatars_props[] = {
+ { "AvatarPersists",
+ GUINT_TO_POINTER (MIXIN_DP_AVATAR_PERSISTS), NULL },
+ { "SupportedAvatarMIMETypes",
+ GUINT_TO_POINTER (MIXIN_DP_SUPPORTED_AVATAR_MIME_TYPES), NULL },
+ { "MinimumAvatarHeight",
+ GUINT_TO_POINTER (MIXIN_DP_MINIMUM_AVATAR_HEIGHT), NULL },
+ { "MinimumAvatarWidth",
+ GUINT_TO_POINTER (MIXIN_DP_MINIMUM_AVATAR_WIDTH), NULL },
+ { "RecommendedAvatarHeight",
+ GUINT_TO_POINTER (MIXIN_DP_RECOMMENDED_AVATAR_HEIGHT), NULL },
+ { "RecommendedAvatarWidth",
+ GUINT_TO_POINTER (MIXIN_DP_RECOMMENDED_AVATAR_WIDTH), NULL },
+ { "MaximumAvatarHeight",
+ GUINT_TO_POINTER (MIXIN_DP_MAXIMUM_AVATAR_HEIGHT), NULL },
+ { "MaximumAvatarWidth",
+ GUINT_TO_POINTER (MIXIN_DP_MAXIMUM_AVATAR_WIDTH), NULL },
+ { "MaximumAvatarBytes",
+ GUINT_TO_POINTER (MIXIN_DP_MAXIMUM_AVATAR_BYTES), NULL },
+ { NULL }
+};
+
+static void
+tp_avatars_mixin_get_dbus_property (GObject *object,
+ GQuark interface,
+ GQuark name,
+ GValue *value,
+ gpointer user_data)
+{
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+
+ switch (GPOINTER_TO_UINT (user_data))
+ {
+ case MIXIN_DP_AVATAR_PERSISTS:
+ g_value_set_boolean (value, self->priv->avatar_persists);
+ break;
+
+ case MIXIN_DP_SUPPORTED_AVATAR_MIME_TYPES:
+ g_value_set_boxed (value, self->priv->requirements->supported_mime_types);
+ break;
+
+ case MIXIN_DP_MINIMUM_AVATAR_HEIGHT:
+ g_value_set_uint (value, self->priv->requirements->minimum_height);
+ break;
+
+ case MIXIN_DP_MINIMUM_AVATAR_WIDTH:
+ g_value_set_uint (value, self->priv->requirements->minimum_width);
+ break;
+
+ case MIXIN_DP_RECOMMENDED_AVATAR_HEIGHT:
+ g_value_set_uint (value, self->priv->requirements->recommended_height);
+ break;
+
+ case MIXIN_DP_RECOMMENDED_AVATAR_WIDTH:
+ g_value_set_uint (value, self->priv->requirements->recommended_width);
+ break;
+
+ case MIXIN_DP_MAXIMUM_AVATAR_HEIGHT:
+ g_value_set_uint (value, self->priv->requirements->maximum_height);
+ break;
+
+ case MIXIN_DP_MAXIMUM_AVATAR_WIDTH:
+ g_value_set_uint (value, self->priv->requirements->maximum_width);
+ break;
+
+ case MIXIN_DP_MAXIMUM_AVATAR_BYTES:
+ g_value_set_uint (value, self->priv->requirements->maximum_bytes);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+/**
+ * tp_avatars_mixin_init_dbus_properties: (skip)
+ * @klass: The class of an object with this mixin
+ *
+ * Set up #TpDBusPropertiesMixinClass to use this mixin's implementation of
+ * the Avatars interface's properties.
+ *
+ * This automatically sets up a list of the supported properties for the
+ * Avatars interface.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_avatars_mixin_init_dbus_properties (GObjectClass *klass)
+{
+ tp_dbus_properties_mixin_implement_interface (klass,
+ TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS,
+ tp_avatars_mixin_get_dbus_property,
+ NULL, known_avatars_props);
+}
+
+static void
+tp_avatars_mixin_refresh_avatars (TpSvcConnectionInterfaceAvatars *iface,
+ const GArray *contacts,
+ DBusGMethodInvocation *context)
+{
+ GObject *object = (GObject *) iface;
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+ TpBaseConnection *conn = TP_BASE_CONNECTION (object);
+ TpHandleRepoIface *contact_repo = tp_base_connection_get_handles (conn,
+ TP_HANDLE_TYPE_CONTACT);
+ GArray *real_contacts;
+ guint i;
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
+
+ if (!tp_handles_are_valid (contact_repo, contacts, FALSE, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ real_contacts = g_array_sized_new (FALSE, FALSE, sizeof (TpHandle),
+ contacts->len);
+
+ /* Keep only contacts for which we don't already have the avatar image */
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, TpHandle, i);
+
+ if (g_hash_table_contains (self->priv->avatars,
+ GUINT_TO_POINTER (contact)))
+ continue;
+
+ g_array_append_val (real_contacts, contact);
+ }
+
+ if (real_contacts->len > 0 &&
+ !self->priv->request_avatars (object, real_contacts, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ }
+ else
+ {
+ tp_svc_connection_interface_avatars_return_from_refresh_avatars (context);
+ }
+
+ g_array_unref (real_contacts);
+}
+
+static void
+tp_avatars_mixin_set_avatar (TpSvcConnectionInterfaceAvatars *iface,
+ const GArray *avatar,
+ const gchar *mime_type,
+ DBusGMethodInvocation *context)
+{
+ GObject *object = (GObject *) iface;
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+ TpBaseConnection *conn = TP_BASE_CONNECTION (object);
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
+
+ if (!self->priv->set_avatar (object, avatar, mime_type, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_svc_connection_interface_avatars_return_from_set_avatar (context);
+}
+
+static void
+tp_avatars_mixin_clear_avatar (TpSvcConnectionInterfaceAvatars *iface,
+ DBusGMethodInvocation *context)
+{
+ GObject *object = (GObject *) iface;
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+ TpBaseConnection *conn = TP_BASE_CONNECTION (object);
+ GError *error = NULL;
+
+ TP_BASE_CONNECTION_ERROR_IF_NOT_CONNECTED (conn, context);
+
+ if (!self->priv->clear_avatar (object, &error))
+ {
+ dbus_g_method_return_error (context, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ tp_svc_connection_interface_avatars_return_from_clear_avatar (context);
+}
+
+/**
+ * tp_avatars_mixin_iface_init: (skip)
+ * @g_iface: A pointer to the #TpSvcConnectionInterfaceAvatarsClass in
+ * an object class
+ * @iface_data: Ignored
+ *
+ * Fill in the vtable entries needed to implement the avatars interface
+ * using this mixin. This function should usually be called via
+ * G_IMPLEMENT_INTERFACE.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_avatars_mixin_iface_init (gpointer g_iface,
+ gpointer iface_data)
+{
+ TpSvcConnectionInterfaceAvatarsClass *klass = g_iface;
+
+#define IMPLEMENT(x) tp_svc_connection_interface_avatars_implement_##x\
+ (klass, tp_avatars_mixin_##x)
+ IMPLEMENT(refresh_avatars);
+ IMPLEMENT(set_avatar);
+ IMPLEMENT(clear_avatar);
+#undef IMPLEMENT
+}
+
+static void
+tp_avatars_mixin_fill_contact_attributes (GObject *object,
+ const GArray *contacts,
+ GHashTable *attributes)
+{
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+ guint i;
+
+ for (i = 0; i < contacts->len; i++)
+ {
+ TpHandle contact = g_array_index (contacts, guint, i);
+ AvatarData *a;
+
+ /* If we don't know the avatar, omit it from reply */
+ if (!g_hash_table_lookup_extended (self->priv->avatars,
+ GUINT_TO_POINTER (contact), NULL, (gpointer *) &a))
+ continue;
+
+ tp_contacts_mixin_set_contact_attribute (attributes, contact,
+ TP_TOKEN_CONNECTION_INTERFACE_AVATARS_AVATAR,
+ tp_g_value_slice_new_string ((a != NULL) ? a->uri : ""));
+ }
+}
+
+/**
+ * tp_avatars_mixin_register_with_contacts_mixin: (skip)
+ * @object: An instance of the implementation that uses both the Contacts
+ * mixin and this mixin
+ *
+ * Register the Avatars interface with the Contacts interface to make it
+ * inspectable. The Contacts mixin should be initialized before this function
+ * is called
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_avatars_mixin_register_with_contacts_mixin (GObject *object)
+{
+ tp_contacts_mixin_add_contact_attributes_iface (object,
+ TP_IFACE_CONNECTION_INTERFACE_AVATARS,
+ tp_avatars_mixin_fill_contact_attributes);
+}
+
+static gboolean
+build_avatar_filename (GObject *object,
+ const gchar *avatar_token,
+ gboolean create_dir,
+ gchar **ret_filename,
+ gchar **ret_mime_filename)
+{
+ TpBaseConnection *base = (TpBaseConnection *) object;
+ gchar *dir;
+ gchar *token_escaped;
+ gboolean success = TRUE;
+
+ token_escaped = tp_escape_as_identifier (avatar_token);
+ dir = g_build_filename (g_get_user_cache_dir (), "telepathy", "avatars",
+ _tp_base_connection_get_cm_name (base),
+ _tp_base_connection_get_protocol_name (base), NULL);
+
+ if (create_dir)
+ {
+ if (g_mkdir_with_parents (dir, 0700) == -1)
+ {
+ DEBUG ("Error creating avatar cache dir: %s", g_strerror (errno));
+ success = FALSE;
+ goto out;
+ }
+ }
+
+ if (ret_filename != NULL)
+ *ret_filename = g_strconcat (dir, G_DIR_SEPARATOR_S, token_escaped, NULL);
+
+ if (ret_mime_filename != NULL)
+ *ret_mime_filename = g_strconcat (dir, G_DIR_SEPARATOR_S, token_escaped,
+ ".mime", NULL);
+
+out:
+ g_free (dir);
+ g_free (token_escaped);
+
+ return success;
+}
+
+static GFile *
+avatar_cache_save (GObject *object,
+ const gchar *avatar_token,
+ const GArray *avatar,
+ const gchar *mime_type)
+{
+ gchar *filename;
+ gchar *mime_filename;
+ GFile *file = NULL;
+ GError *error = NULL;
+
+ if (!build_avatar_filename (object, avatar_token, TRUE, &filename,
+ &mime_filename))
+ return NULL;
+
+ if (!g_file_set_contents (filename, avatar->data, avatar->len, &error))
+ {
+ DEBUG ("Failed to store avatar in cache (%s): %s", filename,
+ error ? error->message : "No error message");
+ g_clear_error (&error);
+ goto OUT;
+ }
+
+ if (!g_file_set_contents (mime_filename, mime_type, -1, &error))
+ {
+ DEBUG ("Failed to store MIME type in cache (%s): %s", mime_filename,
+ error ? error->message : "No error message");
+ g_clear_error (&error);
+ goto OUT;
+ }
+
+ DEBUG ("Avatar stored in cache: %s, %s", filename, mime_type);
+
+ file = g_file_new_for_path (filename);
+
+OUT:
+ g_free (filename);
+ g_free (mime_filename);
+
+ return file;
+}
+
+static GFile *
+avatar_cache_lookup (GObject *object,
+ const gchar *avatar_token)
+{
+ gchar *filename;
+ GFile *file = NULL;
+
+ if (!build_avatar_filename (object, avatar_token,
+ FALSE, &filename, NULL))
+ return NULL;
+
+ if (g_file_test (filename, G_FILE_TEST_EXISTS))
+ {
+ DEBUG ("Avatar found in cache: %s", filename);
+ file = g_file_new_for_path (filename);
+ }
+
+ g_free (filename);
+
+ return file;
+}
+
+static void
+update_avatar_take_data (GObject *object,
+ TpHandle contact,
+ AvatarData *a)
+{
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+ GHashTable *table;
+
+ DEBUG ("Update avatar for handle %u: %s",
+ contact, (a != NULL) ? a->uri : "no avatar");
+
+ g_hash_table_insert (self->priv->avatars, GUINT_TO_POINTER (contact), a);
+ tp_intset_remove (self->priv->needs_request, contact);
+
+ /* FIXME: Could queue to aggregate signals */
+ table = g_hash_table_new (NULL, NULL);
+ g_hash_table_insert (table, GUINT_TO_POINTER (contact),
+ (a != NULL) ? a->uri : "");
+
+ tp_svc_connection_interface_avatars_emit_avatars_updated (object, table);
+
+ g_hash_table_unref (table);
+}
+
+/**
+ * tp_avatars_mixin_avatar_retrieved: (skip)
+ * @object: An instance of the implementation that uses this mixin
+ * @contact: A contact #TpHandle
+ * @token: The new token for @contact's avatar
+ * @data: The new image data for @contact's avatar
+ * @mime_type: The new image MIME type for @contact's avatar, or %NULL if unknown
+ *
+ * Update @contact's avatar. This should be called by the Connection Manager
+ * when avatar data is received from the server for any contact.
+ *
+ * The image is stored on a disk cache, to avoid unnecessary future refetch of
+ * the data from the server.
+ *
+ * Use tp_avatars_mixin_avatar_changed() in the case the avatar data is unknown.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_avatars_mixin_avatar_retrieved (GObject *object,
+ TpHandle contact,
+ const gchar *token,
+ GArray *data,
+ const gchar *mime_type)
+{
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+ AvatarData *a;
+ GFile *file;
+
+ g_return_if_fail (contact != 0);
+ g_return_if_fail (!tp_str_empty (token));
+ g_return_if_fail (data != NULL);
+
+ /* Check if we already have the same in memory */
+ a = g_hash_table_lookup (self->priv->avatars, GUINT_TO_POINTER (contact));
+ if (a != NULL && !tp_strdiff (a->token, token))
+ return;
+
+ /* Store on disk cache */
+ file = avatar_cache_save (object, token, data, mime_type);
+
+ /* Update */
+ update_avatar_take_data (object, contact,
+ avatar_data_new (token, file));
+
+ g_object_unref (file);
+}
+
+/**
+ * tp_avatars_mixin_avatar_changed: (skip)
+ * @object: An instance of the implementation that uses this mixin
+ * @contact: A contact #TpHandle
+ * @token: The new token for @contact's avatar
+ *
+ * Update @contact's avatar. This should be called by the Connection Manager
+ * when it knows that the avatar image changed, but did not receive the image
+ * data. If the avatar got removed, then this should be called with %NULL
+ * @token.
+ *
+ * If @token is not empty and the image data is found on disk cache, it will be
+ * used. Otherwise request_avatars virtual method will be called to fetch the
+ * avatar from server.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_avatars_mixin_avatar_changed (GObject *object,
+ TpHandle contact,
+ const gchar *token)
+{
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+ TpBaseConnection *base = (TpBaseConnection *) object;
+ AvatarData *a;
+ GFile *file;
+
+ g_return_if_fail (contact != 0);
+
+ /* Avoid confusion between NULL and "" */
+ if (tp_str_empty (token))
+ token = NULL;
+
+ /* Check if we already have the same in memory */
+ if (g_hash_table_lookup_extended (self->priv->avatars,
+ GUINT_TO_POINTER (contact), NULL, (gpointer *) &a) &&
+ ((a == NULL && token == NULL) ||
+ (a != NULL && !tp_strdiff (a->token, token))))
+ return;
+
+ /* Avatar has been removed? */
+ if (token == NULL)
+ {
+ update_avatar_take_data (object, contact, NULL);
+ return;
+ }
+
+ /* There is an avatar set, search it in the cache */
+ file = avatar_cache_lookup (object, token);
+ if (file == NULL)
+ {
+ GArray *handles;
+
+ /* Avatar not found in cache. Request the avatar if it's for self contact
+ * or if a client claims interest in avatars. Keep the last known avatar
+ * in the meantime. */
+ if (contact != tp_base_connection_get_self_handle (base) &&
+ !_tp_base_connection_has_client_interest (base,
+ TP_IFACE_QUARK_CONNECTION_INTERFACE_AVATARS))
+ {
+ tp_intset_add (self->priv->needs_request, contact);
+ return;
+ }
+
+ /* FIXME: Could queue to aggregate calls */
+ handles = g_array_new (FALSE, FALSE, sizeof (TpHandle));
+ g_array_append_val (handles, contact);
+
+ self->priv->request_avatars (object, handles, NULL);
+
+ g_array_unref (handles);
+ return;
+ }
+
+ update_avatar_take_data (object, contact,
+ avatar_data_new (token, file));
+
+ g_object_unref (file);
+}
+
+/**
+ * tp_avatars_mixin_drop_avatar: (skip)
+ * @object: An instance of the implementation that uses this mixin
+ * @contact: A contact #TpHandle
+ *
+ * To be called to free allocated memory when the contact's avatar is not
+ * relevant anymore. For example when the contact is removed from roster, or
+ * when a channel with channel-specific contacts is left.
+ *
+ * With XMPP, this could also be called when a contact goes offline because its
+ * avatar is not known anymore.
+ *
+ * Note that this won't tell the client about the change, so last known avatar
+ * will still be displayed. If it is known that the contact has no avatar,
+ * tp_avatars_mixin_avatar_changed() with %NULL token should be used instead.
+ *
+ * Since: 0.UNRELEASED
+ */
+void
+tp_avatars_mixin_drop_avatar (GObject *object,
+ TpHandle contact)
+{
+ TpAvatarsMixin *self = TP_AVATARS_MIXIN (object);
+
+ g_hash_table_remove (self->priv->avatars, GUINT_TO_POINTER (contact));
+ tp_intset_remove (self->priv->needs_request, contact);
+}
diff --git a/telepathy-glib/avatars-mixin.h b/telepathy-glib/avatars-mixin.h
new file mode 100644
index 000000000..84c44a64d
--- /dev/null
+++ b/telepathy-glib/avatars-mixin.h
@@ -0,0 +1,81 @@
+/*
+ * avatars-mixin.h - Header for TpAvatarsMixin
+ * Copyright (C) 2012 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
+ */
+
+#ifndef __TP_AVATARS_MIXIN_H__
+#define __TP_AVATARS_MIXIN_H__
+
+#include <telepathy-glib/enums.h>
+#include <telepathy-glib/handle.h>
+#include <telepathy-glib/connection.h>
+
+G_BEGIN_DECLS
+
+typedef struct _TpAvatarsMixin TpAvatarsMixin;
+typedef struct _TpAvatarsMixinPrivate TpAvatarsMixinPrivate;
+
+typedef gboolean (*TpAvatarsMixinSetAvatarFunc) (GObject *object,
+ const GArray *avatar,
+ const gchar *mime_type,
+ GError **error);
+typedef gboolean (*TpAvatarsMixinClearAvatarFunc) (GObject *object,
+ GError **error);
+typedef gboolean (*TpAvatarsMixinRequestAvatarsFunc) (GObject *object,
+ const GArray *contacts,
+ GError **error);
+
+struct _TpAvatarsMixin {
+ /*<private>*/
+ TpAvatarsMixinPrivate *priv;
+};
+
+/* Update avatar */
+void tp_avatars_mixin_avatar_retrieved (GObject *object,
+ TpHandle contact,
+ const gchar *token,
+ GArray *data,
+ const gchar *mime_type);
+
+void tp_avatars_mixin_avatar_changed (GObject *object,
+ TpHandle contact,
+ const gchar *token);
+
+void tp_avatars_mixin_drop_avatar (GObject *object,
+ TpHandle contact);
+
+/* Initialisation */
+void tp_avatars_mixin_init (GObject *object,
+ glong offset,
+ TpAvatarsMixinSetAvatarFunc set_avatar,
+ TpAvatarsMixinClearAvatarFunc clear_avatar,
+ TpAvatarsMixinRequestAvatarsFunc request_avatars,
+ gboolean avatar_persists,
+ TpAvatarRequirements *requirements);
+
+void tp_avatars_mixin_finalize (GObject *object);
+
+void tp_avatars_mixin_iface_init (gpointer g_iface,
+ gpointer iface_data);
+
+void tp_avatars_mixin_init_dbus_properties (GObjectClass *klass);
+
+void tp_avatars_mixin_register_with_contacts_mixin (GObject *object);
+
+G_END_DECLS
+
+#endif /* #ifndef __TP_AVATARS_MIXIN_H__ */
diff --git a/telepathy-glib/base-connection-internal.h b/telepathy-glib/base-connection-internal.h
index cd731d4cf..fec06a67d 100644
--- a/telepathy-glib/base-connection-internal.h
+++ b/telepathy-glib/base-connection-internal.h
@@ -33,6 +33,12 @@ void _tp_base_connection_set_handle_repo (TpBaseConnection *self,
gpointer _tp_base_connection_find_channel_manager (TpBaseConnection *self,
GType type);
+gboolean _tp_base_connection_has_client_interest (TpBaseConnection *self,
+ GQuark token);
+
+const gchar *_tp_base_connection_get_cm_name (TpBaseConnection *self);
+const gchar *_tp_base_connection_get_protocol_name (TpBaseConnection *self);
+
G_END_DECLS
#endif
diff --git a/telepathy-glib/base-connection.c b/telepathy-glib/base-connection.c
index 52e2c3726..27a33e450 100644
--- a/telepathy-glib/base-connection.c
+++ b/telepathy-glib/base-connection.c
@@ -418,6 +418,8 @@ struct _TpBaseConnectionPrivate
/* GQuark iface => GHashTable {
* unique name borrowed from interested_clients => gsize count } */
GHashTable *client_interests;
+
+ gchar *cm_name;
};
static const gchar * const *tp_base_connection_get_interfaces (
@@ -601,6 +603,7 @@ tp_base_connection_finalize (GObject *object)
TpBaseConnection *self = TP_BASE_CONNECTION (object);
TpBaseConnectionPrivate *priv = self->priv;
+ g_free (priv->cm_name);
g_free (priv->protocol);
g_free (priv->bus_name);
g_free (priv->object_path);
@@ -1014,6 +1017,14 @@ tp_base_connection_add_possible_client_interest (TpBaseConnection *self,
g_hash_table_new (g_str_hash, g_str_equal));
}
+gboolean
+_tp_base_connection_has_client_interest (TpBaseConnection *self,
+ GQuark token)
+{
+ return g_hash_table_contains (self->priv->client_interests,
+ GUINT_TO_POINTER (token));
+}
+
/* D-Bus properties for the Requests interface */
static void
@@ -1468,6 +1479,7 @@ tp_base_connection_register (TpBaseConnection *self,
return FALSE;
}
+ priv->cm_name = g_strdup (cm_name);
priv->bus_name = g_strdup_printf (TP_CONN_BUS_NAME_BASE "%s.%s.%s",
cm_name, safe_proto, unique_name);
g_assert (strlen (priv->bus_name) <= 255);
@@ -3005,3 +3017,15 @@ tp_base_connection_get_object_path (TpBaseConnection *self)
return self->priv->object_path;
}
+
+const gchar *
+_tp_base_connection_get_cm_name (TpBaseConnection *self)
+{
+ return self->priv->cm_name;
+}
+
+const gchar *
+_tp_base_connection_get_protocol_name (TpBaseConnection *self)
+{
+ return self->priv->protocol;
+}
diff --git a/telepathy-glib/connection-internal.h b/telepathy-glib/connection-internal.h
index 03a1d9b63..757c3fb5c 100644
--- a/telepathy-glib/connection-internal.h
+++ b/telepathy-glib/connection-internal.h
@@ -63,8 +63,6 @@ struct _TpConnectionPrivate {
GQueue capabilities_queue;
TpAvatarRequirements *avatar_requirements;
- GArray *avatar_request_queue;
- guint avatar_request_idle_id;
TpContactInfoFlags contact_info_flags;
GList *contact_info_supported_fields;
diff --git a/telepathy-glib/connection.c b/telepathy-glib/connection.c
index 704e0d322..f41587085 100644
--- a/telepathy-glib/connection.c
+++ b/telepathy-glib/connection.c
@@ -1205,18 +1205,6 @@ tp_connection_finalize (GObject *object)
self->priv->connection_error_details = NULL;
}
- if (self->priv->avatar_request_queue != NULL)
- {
- g_array_unref (self->priv->avatar_request_queue);
- self->priv->avatar_request_queue = NULL;
- }
-
- if (self->priv->avatar_request_idle_id != 0)
- {
- g_source_remove (self->priv->avatar_request_idle_id);
- self->priv->avatar_request_idle_id = 0;
- }
-
tp_contact_info_spec_list_free (self->priv->contact_info_supported_fields);
self->priv->contact_info_supported_fields = NULL;
diff --git a/telepathy-glib/telepathy-glib.h b/telepathy-glib/telepathy-glib.h
index a638fc7fa..e35d94c4f 100644
--- a/telepathy-glib/telepathy-glib.h
+++ b/telepathy-glib/telepathy-glib.h
@@ -37,6 +37,7 @@
#include <telepathy-glib/account.h>
#include <telepathy-glib/add-dispatch-operation-context.h>
#include <telepathy-glib/automatic-client-factory.h>
+#include <telepathy-glib/avatars-mixin.h>
#include <telepathy-glib/base-call-channel.h>
#include <telepathy-glib/base-call-content.h>
#include <telepathy-glib/base-call-stream.h>