[libgdata] [contacts] Add contact photo support
- From: Philip Withnall <pwithnall src gnome org>
- To: svn-commits-list gnome org
- Subject: [libgdata] [contacts] Add contact photo support
- Date: Mon, 1 Jun 2009 19:16:49 -0400 (EDT)
commit 59fca6a0a7fc0f68f0767c4260d3c0dd9625b635
Author: Philip Withnall <philip tecnocode co uk>
Date: Tue Jun 2 00:15:49 2009 +0100
[contacts] Add contact photo support
Add support for getting, setting and removing contact photos. Full test cases
and documentation are included.
---
docs/reference/gdata-sections.txt | 3 +
gdata/gdata.symbols | 3 +
gdata/services/contacts/gdata-contacts-contact.c | 245 +++++++++++++++++++++-
gdata/services/contacts/gdata-contacts-contact.h | 8 +
gdata/services/contacts/gdata-contacts-service.h | 3 +-
gdata/tests/contacts.c | 156 ++++++++++++++
gdata/tests/photo.jpg | Bin 0 -> 17659 bytes
7 files changed, 413 insertions(+), 5 deletions(-)
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 79fac71..a26c4c1 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -429,6 +429,9 @@ gdata_contacts_contact_get_extended_properties
gdata_contacts_contact_set_extended_property
gdata_contacts_contact_get_edited
gdata_contacts_contact_is_deleted
+gdata_contacts_contact_has_photo
+gdata_contacts_contact_get_photo
+gdata_contacts_contact_set_photo
<SUBSECTION Standard>
gdata_contacts_contact_get_type
GDATA_CONTACTS_CONTACT
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 404af68..349e904 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -351,6 +351,9 @@ gdata_contacts_contact_remove_group
gdata_contacts_contact_is_group_deleted
gdata_contacts_contact_get_groups
gdata_contacts_contact_is_deleted
+gdata_contacts_contact_has_photo
+gdata_contacts_contact_get_photo
+gdata_contacts_contact_set_photo
gdata_access_handler_get_type
gdata_access_handler_get_rules
gdata_access_handler_insert_rule
diff --git a/gdata/services/contacts/gdata-contacts-contact.c b/gdata/services/contacts/gdata-contacts-contact.c
index 8ac6762..c87b5d1 100644
--- a/gdata/services/contacts/gdata-contacts-contact.c
+++ b/gdata/services/contacts/gdata-contacts-contact.c
@@ -63,11 +63,13 @@ struct _GDataContactsContactPrivate {
GHashTable *extended_properties;
GHashTable *groups;
gboolean deleted;
+ gchar *photo_etag;
};
enum {
PROP_EDITED = 1,
- PROP_DELETED
+ PROP_DELETED,
+ PROP_HAS_PHOTO
};
G_DEFINE_TYPE (GDataContactsContact, gdata_contacts_contact, GDATA_TYPE_ENTRY)
@@ -118,6 +120,19 @@ gdata_contacts_contact_class_init (GDataContactsContactClass *klass)
"Deleted", "Whether the entry has been deleted.",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataContactsContact:has-photo:
+ *
+ * Whether the contact has a photo.
+ *
+ * Since: 0.4.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_HAS_PHOTO,
+ g_param_spec_boolean ("has-photo",
+ "Has photo?", "Whether the contact has a photo.",
+ FALSE,
+ G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
}
static void
@@ -145,6 +160,7 @@ gdata_contacts_contact_finalize (GObject *object)
g_list_free (priv->organizations);
g_hash_table_destroy (priv->extended_properties);
g_hash_table_destroy (priv->groups);
+ g_free (priv->photo_etag);
/* Chain up to the parent class */
G_OBJECT_CLASS (gdata_contacts_contact_parent_class)->finalize (object);
@@ -162,6 +178,9 @@ gdata_contacts_contact_get_property (GObject *object, guint property_id, GValue
case PROP_DELETED:
g_value_set_boolean (value, priv->deleted);
break;
+ case PROP_HAS_PHOTO:
+ g_value_set_boolean (value, (priv->photo_etag != NULL) ? TRUE : FALSE);
+ break;
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -488,9 +507,26 @@ parse_xml (GDataParsable *parsable, xmlDoc *doc, xmlNode *node, gpointer user_da
} else if (xmlStrcmp (node->name, (xmlChar*) "deleted") == 0) {
/* gd:deleted */
self->priv->deleted = TRUE;
- } else if (GDATA_PARSABLE_CLASS (gdata_contacts_contact_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
- /* Error! */
- return FALSE;
+ } else {
+ /* If we haven't yet found a photo, check to see if it's a photo <link> element */
+ if (self->priv->photo_etag == NULL && xmlStrcmp (node->name, (xmlChar*) "link") == 0) {
+ xmlChar *rel = xmlGetProp (node, (xmlChar*) "rel");
+ if (xmlStrcmp (rel, (xmlChar*) "http://schemas.google.com/contacts/2008/rel#photo") == 0) {
+ xmlChar *etag;
+
+ /* It's the photo link (http://code.google.com/apis/contacts/docs/2.0/reference.html#Photos), whose ETag we should
+ * note down, then pass onto the parent class to parse properly */
+ etag = xmlGetProp (node, (xmlChar*) "etag");
+ self->priv->photo_etag = g_strdup ((gchar*) etag);
+ xmlFree (etag);
+ }
+ xmlFree (rel);
+ }
+
+ if (GDATA_PARSABLE_CLASS (gdata_contacts_contact_parent_class)->parse_xml (parsable, doc, node, user_data, error) == FALSE) {
+ /* Error! */
+ return FALSE;
+ }
}
return TRUE;
@@ -1209,3 +1245,204 @@ gdata_contacts_contact_is_deleted (GDataContactsContact *self)
g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (self), FALSE);
return self->priv->deleted;
}
+
+/**
+ * gdata_contacts_contact_has_photo:
+ * @self: a #GDataContactsContact
+ *
+ * Returns whether the contact has a photo attached to their contact entry. If the contact
+ * does have a photo, it can be returned using gdata_contacts_contact_get_photo().
+ *
+ * Return value: %TRUE if the contact has a photo, %FALSE otherwise
+ *
+ * Since: 0.4.0
+ **/
+gboolean
+gdata_contacts_contact_has_photo (GDataContactsContact *self)
+{
+ g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (self), FALSE);
+ return (self->priv->photo_etag != NULL) ? TRUE : FALSE;
+}
+
+/**
+ * gdata_contacts_contact_get_photo:
+ * @self: a #GDataContactsContact
+ * @service: a #GDataContactsService
+ * @length: return location for the image length, in bytes
+ * @content_type: return location for the image's content type, or %NULL; free with g_free()
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Downloads and returns the contact's photo, if they have one. If the contact doesn't
+ * have a photo (i.e. gdata_contacts_contact_has_photo() returns %FALSE), %NULL is returned, but
+ * no error is set in @error.
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If there is an error getting the photo, a %GDATA_SERVICE_ERROR_WITH_QUERY error will be returned.
+ *
+ * Return value: the image data, or %NULL; free with g_free()
+ *
+ * Since: 0.4.0
+ **/
+gchar *
+gdata_contacts_contact_get_photo (GDataContactsContact *self, GDataContactsService *service, gsize *length, gchar **content_type,
+ GCancellable *cancellable, GError **error)
+{
+ GDataServiceClass *klass;
+ GDataLink *link;
+ SoupMessage *message;
+ guint status;
+ gchar *data;
+
+ /* TODO: async version */
+ g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (self), NULL);
+ g_return_val_if_fail (GDATA_IS_CONTACTS_SERVICE (service), NULL);
+ g_return_val_if_fail (length != NULL, NULL);
+
+ /* Return if there is no photo */
+ if (gdata_contacts_contact_has_photo (self) == FALSE)
+ return NULL;
+
+ /* Get the photo URI */
+ link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/contacts/2008/rel#photo");
+ g_assert (link != NULL);
+ message = soup_message_new (SOUP_METHOD_GET, link->href);
+
+ /* Make sure the headers are set */
+ klass = GDATA_SERVICE_GET_CLASS (service);
+ if (klass->append_query_headers != NULL)
+ klass->append_query_headers (GDATA_SERVICE (service), message);
+
+ /* Send the message */
+ status = _gdata_service_send_message (GDATA_SERVICE (service), message, error);
+ if (status == SOUP_STATUS_NONE) {
+ g_object_unref (message);
+ return NULL;
+ }
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_object_unref (message);
+ return NULL;
+ }
+
+ if (status != 200) {
+ /* Error */
+ g_assert (klass->parse_error_response != NULL);
+ klass->parse_error_response (GDATA_SERVICE (service), GDATA_SERVICE_ERROR_WITH_QUERY, status, message->reason_phrase,
+ message->response_body->data, message->response_body->length, error);
+ g_object_unref (message);
+ return NULL;
+ }
+
+ g_assert (message->response_body->data != NULL);
+
+ /* Sort out the return values */
+ if (content_type != NULL)
+ *content_type = g_strdup (soup_message_headers_get_content_type (message->response_headers, NULL));
+ *length = message->response_body->length;
+ data = g_memdup (message->response_body->data, message->response_body->length);
+
+ /* Update the stored photo ETag */
+ g_free (self->priv->photo_etag);
+ self->priv->photo_etag = g_strdup (soup_message_headers_get_one (message->response_headers, "ETag"));
+ g_object_unref (message);
+
+ return data;
+}
+
+/**
+ * gdata_contacts_contact_set_photo:
+ * @self: a #GDataContactsContact
+ * @service: a #GDataService
+ * @data: the image data, or %NULL
+ * @length: the image length, in bytes, or %0
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Sets the contact's photo to @data or, if @data is %NULL, deletes the contact's photo.
+ *
+ * If @cancellable is not %NULL, then the operation can be cancelled by triggering the @cancellable object from another thread.
+ * If the operation was cancelled, the error %G_IO_ERROR_CANCELLED will be returned.
+ *
+ * If there is an error setting the photo, a %GDATA_SERVICE_ERROR_WITH_UPDATE error will be returned.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ *
+ * Since: 0.4.0
+ **/
+gboolean
+gdata_contacts_contact_set_photo (GDataContactsContact *self, GDataService *service, gchar *data, gsize length,
+ GCancellable *cancellable, GError **error)
+{
+ GDataServiceClass *klass;
+ GDataLink *link;
+ SoupMessage *message;
+ guint status;
+ gboolean adding_photo = FALSE, deleting_photo = FALSE;
+
+ /* TODO: async version */
+ g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (self), FALSE);
+ g_return_val_if_fail (GDATA_IS_SERVICE (service), FALSE);
+
+ if (self->priv->photo_etag == NULL && data != NULL)
+ adding_photo = TRUE;
+ else if (self->priv->photo_etag != NULL && data == NULL)
+ deleting_photo = TRUE;
+
+ /* Get the photo URI */
+ link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/contacts/2008/rel#photo");
+ g_assert (link != NULL);
+ if (deleting_photo == TRUE)
+ message = soup_message_new (SOUP_METHOD_DELETE, link->href);
+ else
+ message = soup_message_new (SOUP_METHOD_PUT, link->href);
+
+ /* Make sure the headers are set */
+ klass = GDATA_SERVICE_GET_CLASS (service);
+ if (klass->append_query_headers != NULL)
+ klass->append_query_headers (service, message);
+
+ /* Append the ETag header if possible */
+ if (self->priv->photo_etag != NULL)
+ soup_message_headers_append (message->request_headers, "If-Match", self->priv->photo_etag);
+
+ if (deleting_photo == FALSE) {
+ /* Append the data */
+ soup_message_set_request (message, "image/*", SOUP_MEMORY_STATIC, (gchar*) data, length);
+ }
+
+ /* Send the message */
+ status = _gdata_service_send_message (service, message, error);
+ if (status == SOUP_STATUS_NONE) {
+ g_object_unref (message);
+ return FALSE;
+ }
+
+ /* Check for cancellation */
+ if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+ g_object_unref (message);
+ return FALSE;
+ }
+
+ if (status != 200) {
+ /* Error */
+ g_assert (klass->parse_error_response != NULL);
+ klass->parse_error_response (service, GDATA_SERVICE_ERROR_WITH_UPDATE, status, message->reason_phrase, message->response_body->data,
+ message->response_body->length, error);
+ g_object_unref (message);
+ return FALSE;
+ }
+
+ /* Update the stored photo ETag */
+ g_free (self->priv->photo_etag);
+ self->priv->photo_etag = g_strdup (soup_message_headers_get_one (message->response_headers, "ETag"));
+ g_object_unref (message);
+
+ if (adding_photo == TRUE || deleting_photo == TRUE)
+ g_object_notify (G_OBJECT (self), "has-photo");
+
+ return TRUE;
+}
diff --git a/gdata/services/contacts/gdata-contacts-contact.h b/gdata/services/contacts/gdata-contacts-contact.h
index ecf6b75..f3e9b5c 100644
--- a/gdata/services/contacts/gdata-contacts-contact.h
+++ b/gdata/services/contacts/gdata-contacts-contact.h
@@ -94,6 +94,14 @@ void gdata_contacts_contact_remove_group (GDataContactsContact *self, const gcha
gboolean gdata_contacts_contact_is_group_deleted (GDataContactsContact *self, const gchar *href);
GList *gdata_contacts_contact_get_groups (GDataContactsContact *self) G_GNUC_WARN_UNUSED_RESULT;
+#include <gdata/services/contacts/gdata-contacts-service.h>
+
+gboolean gdata_contacts_contact_has_photo (GDataContactsContact *self);
+gchar *gdata_contacts_contact_get_photo (GDataContactsContact *self, GDataContactsService *service, gsize *length, gchar **content_type,
+ GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+gboolean gdata_contacts_contact_set_photo (GDataContactsContact *self, GDataService *service, gchar *data, gsize length,
+ GCancellable *cancellable, GError **error);
+
G_END_DECLS
#endif /* !GDATA_CONTACTS_CONTACT_H */
diff --git a/gdata/services/contacts/gdata-contacts-service.h b/gdata/services/contacts/gdata-contacts-service.h
index 7a64c18..eea077e 100644
--- a/gdata/services/contacts/gdata-contacts-service.h
+++ b/gdata/services/contacts/gdata-contacts-service.h
@@ -25,7 +25,6 @@
#include <gdata/gdata-service.h>
#include <gdata/gdata-query.h>
-#include <gdata/services/contacts/gdata-contacts-contact.h>
G_BEGIN_DECLS
@@ -68,6 +67,8 @@ void gdata_contacts_service_query_contacts_async (GDataContactsService *self, GD
GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
GAsyncReadyCallback callback, gpointer user_data);
+#include <gdata/services/contacts/gdata-contacts-contact.h>
+
GDataContactsContact *gdata_contacts_service_insert_contact (GDataContactsService *self, GDataContactsContact *contact,
GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
diff --git a/gdata/tests/contacts.c b/gdata/tests/contacts.c
index fb4104e..cf41c1a 100644
--- a/gdata/tests/contacts.c
+++ b/gdata/tests/contacts.c
@@ -27,6 +27,32 @@
static GDataService *service = NULL;
static GMainLoop *main_loop = NULL;
+static GDataContactsContact *
+get_contact (void)
+{
+ GDataFeed *feed;
+ GDataEntry *entry;
+ GList *entries;
+ GError *error = NULL;
+
+ g_assert (service != NULL);
+
+ feed = gdata_contacts_service_query_contacts (GDATA_CONTACTS_SERVICE (service), NULL, NULL, NULL, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_FEED (feed));
+ g_clear_error (&error);
+
+ entries = gdata_feed_get_entries (feed);
+ g_assert (entries != NULL);
+ entry = entries->data;
+ g_assert (GDATA_IS_CONTACTS_CONTACT (entry));
+
+ g_object_ref (entry);
+ g_object_unref (feed);
+
+ return GDATA_CONTACTS_CONTACT (entry);
+}
+
static void
test_authentication (void)
{
@@ -239,6 +265,130 @@ test_parser_minimal (void)
g_assert (*gdata_entry_get_title (GDATA_ENTRY (contact)) == '\0');
/* TODO: Check the other properties */
+
+ g_object_unref (contact);
+}
+
+static void
+test_photo_has_photo (void)
+{
+ GDataContactsContact *contact;
+ gsize length = 0;
+ gchar *content_type = NULL;
+ GError *error = NULL;
+
+ contact = gdata_contacts_contact_new_from_xml (
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005'>"
+ "<id>http://www.google.com/m8/feeds/contacts/libgdata test googlemail com/base/1b46cdd20bfbee3b</id>"
+ "<updated>2009-04-25T15:21:53.688Z</updated>"
+ "<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact'/>"
+ "<title></title>" /* Here's where it all went wrong */
+ "<link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*' "
+ "href='http://www.google.com/m8/feeds/photos/media/libgdata test googlemail com/1b46cdd20bfbee3b'/>"
+ "</entry>", -1, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_ENTRY (contact));
+ g_clear_error (&error);
+
+ /* Check for no photo */
+ g_assert (gdata_contacts_contact_has_photo (contact) == FALSE);
+ g_assert (gdata_contacts_contact_get_photo (contact, GDATA_CONTACTS_SERVICE (service), &length, &content_type, NULL, &error) == NULL);
+ g_assert_cmpint (length, ==, 0);
+ g_assert (content_type == NULL);
+ g_assert_no_error (error);
+
+ g_clear_error (&error);
+ g_free (content_type);
+ g_object_unref (contact);
+
+ /* Try again with a photo */
+ contact = gdata_contacts_contact_new_from_xml (
+ "<entry xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gd='http://schemas.google.com/g/2005'>"
+ "<id>http://www.google.com/m8/feeds/contacts/libgdata test googlemail com/base/1b46cdd20bfbee3b</id>"
+ "<updated>2009-04-25T15:21:53.688Z</updated>"
+ "<category scheme='http://schemas.google.com/g/2005#kind' term='http://schemas.google.com/contact/2008#contact'/>"
+ "<title></title>" /* Here's where it all went wrong */
+ "<link rel='http://schemas.google.com/contacts/2008/rel#photo' type='image/*' "
+ "href='http://www.google.com/m8/feeds/photos/media/libgdata test googlemail com/1b46cdd20bfbee3b' "
+ "gd:etag='"QngzcDVSLyp7ImA9WxJTFkoITgU."'/>"
+ "</entry>", -1, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_ENTRY (contact));
+ g_clear_error (&error);
+
+ g_assert (gdata_contacts_contact_has_photo (contact) == TRUE);
+ g_object_unref (contact);
+}
+
+static void
+test_photo_add (void)
+{
+ GDataContactsContact *contact;
+ gchar *data;
+ gsize length;
+ gboolean retval;
+ GError *error = NULL;
+
+ /* Get the photo */
+ /* TODO: Fix the path */
+ g_assert (g_file_get_contents ("/home/philip/Development/libgdata/gdata/tests/photo.jpg", &data, &length, NULL) == TRUE);
+
+ /* Add it to the contact */
+ contact = get_contact ();
+ retval = gdata_contacts_contact_set_photo (contact, service, data, length, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (retval == TRUE);
+
+ g_clear_error (&error);
+ g_object_unref (contact);
+ g_free (data);
+}
+
+static void
+test_photo_get (void)
+{
+ GDataContactsContact *contact;
+ gchar *data, *content_type = NULL;
+ gsize length = 0;
+ GError *error = NULL;
+
+ contact = get_contact ();
+ g_assert (gdata_contacts_contact_has_photo (contact) == TRUE);
+
+ /* Get the photo from the network */
+ data = gdata_contacts_contact_get_photo (contact, GDATA_CONTACTS_SERVICE (service), &length, &content_type, NULL, &error);
+ g_assert_no_error (error);
+ g_assert (data != NULL);
+ g_assert (length != 0);
+ g_assert_cmpstr (content_type, ==, "image/jpg");
+
+ g_assert (gdata_contacts_contact_has_photo (contact) == TRUE);
+
+ g_free (content_type);
+ g_free (data);
+ g_object_unref (contact);
+ g_clear_error (&error);
+}
+
+static void
+test_photo_delete (void)
+{
+ GDataContactsContact *contact;
+ GError *error = NULL;
+
+ contact = get_contact ();
+ g_assert (gdata_contacts_contact_has_photo (contact) == TRUE);
+
+ /* Remove the contact's photo */
+ g_assert (gdata_contacts_contact_set_photo (contact, service, NULL, 0, NULL, &error) == TRUE);
+ g_assert_no_error (error);
+
+ g_assert (gdata_contacts_contact_has_photo (contact) == FALSE);
+
+ g_clear_error (&error);
+ g_object_unref (contact);
}
int
@@ -259,6 +409,12 @@ main (int argc, char *argv[])
g_test_add_func ("/contacts/insert/simple", test_insert_simple);
g_test_add_func ("/contacts/query/uri", test_query_uri);
g_test_add_func ("/contacts/parser/minimal", test_parser_minimal);
+ g_test_add_func ("/contacts/photo/has_photo", test_photo_has_photo);
+ if (g_test_slow () == TRUE) {
+ g_test_add_func ("/contacts/photo/add", test_photo_add);
+ g_test_add_func ("/contacts/photo/get", test_photo_get);
+ g_test_add_func ("/contacts/photo/delete", test_photo_delete);
+ }
retval = g_test_run ();
if (service != NULL)
diff --git a/gdata/tests/photo.jpg b/gdata/tests/photo.jpg
new file mode 100644
index 0000000..281171d
Binary files /dev/null and b/gdata/tests/photo.jpg differ
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]