[libsocialweb] contacts: add SwContactView and SwContact



commit 0b4d4657d30439499a6bc301162afd6296d9cb2f
Author: Alban Crequy <alban crequy collabora co uk>
Date:   Wed Mar 23 13:25:13 2011 +0000

    contacts: add SwContactView and SwContact

 libsocialweb/Makefile.am       |    4 +
 libsocialweb/sw-contact-view.c |  832 ++++++++++++++++++++++++++++++++++++++++
 libsocialweb/sw-contact-view.h |   71 ++++
 libsocialweb/sw-contact.c      |  493 ++++++++++++++++++++++++
 libsocialweb/sw-contact.h      |  105 +++++
 libsocialweb/sw-debug.c        |    1 +
 libsocialweb/sw-debug.h        |   19 +-
 libsocialweb/sw-types.h        |    2 +
 8 files changed, 1518 insertions(+), 9 deletions(-)
---
diff --git a/libsocialweb/Makefile.am b/libsocialweb/Makefile.am
index 526fc47..284543a 100644
--- a/libsocialweb/Makefile.am
+++ b/libsocialweb/Makefile.am
@@ -21,6 +21,8 @@ libsocialweb_la_LIBADD = $(DBUS_GLIB_LIBS) $(SOUP_LIBS) $(SOUP_GNOME_LIBS) \
 libsocialweb_la_SOURCES = sw-types.h \
 		       sw-debug.c sw-debug.h \
 		       sw-core.c sw-core.h \
+		       sw-contact.c sw-contact.h \
+		       sw-contact-view.c sw-contact-view.h \
 		       sw-item.c sw-item.h \
 		       sw-item-view.c sw-item-view.h \
 		       sw-item-stream.c sw-item-stream.h \
@@ -41,12 +43,14 @@ public_headers = \
 	sw-types.h \
 	sw-service.h \
 	sw-online.h \
+	sw-contact-view.h \
 	sw-item-view.h \
 	sw-item-stream.h \
 	sw-debug.h \
 	sw-web.h \
 	sw-set.h \
 	sw-cache.h \
+	sw-contact.h \
 	sw-item.h \
 	sw-module.h \
 	sw-utils.h \
diff --git a/libsocialweb/sw-contact-view.c b/libsocialweb/sw-contact-view.c
new file mode 100644
index 0000000..0091d92
--- /dev/null
+++ b/libsocialweb/sw-contact-view.c
@@ -0,0 +1,832 @@
+/*
+ * libsocialweb - social data store
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * Author: Rob Bradford <rob linux intel com>
+ *         Alban Crequy <alban crequy collabora co uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "sw-debug.h"
+#include "sw-contact-view.h"
+#include "sw-contact-view-ginterface.h"
+
+#include <libsocialweb/sw-utils.h>
+#include <libsocialweb/sw-core.h>
+
+static void sw_contact_view_iface_init (gpointer g_iface, gpointer iface_data);
+G_DEFINE_TYPE_WITH_CODE (SwContactView, sw_contact_view, G_TYPE_OBJECT,
+                         G_IMPLEMENT_INTERFACE (SW_TYPE_CONTACT_VIEW_IFACE,
+                                                sw_contact_view_iface_init));
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_CONTACT_VIEW, SwContactViewPrivate))
+
+typedef struct _SwContactViewPrivate SwContactViewPrivate;
+
+struct _SwContactViewPrivate {
+  SwService *service;
+  gchar *object_path;
+  SwSet *current_contacts_set;
+  SwSet *pending_contacts_set;
+
+  /* timeout used for coalescing multiple delayed ready additions */
+  guint pending_timeout_id;
+
+  /* timeout used for ratelimiting checking for changed contacts */
+  guint refresh_timeout_id;
+
+  GHashTable *uid_to_contacts;
+
+  GList *changed_contacts;
+};
+
+enum
+{
+  PROP_0,
+  PROP_SERVICE,
+  PROP_OBJECT_PATH
+};
+
+static void sw_contact_view_add_contacts (SwContactView *contact_view,
+                                    GList      *contacts);
+static void sw_contact_view_update_contacts (SwContactView *contact_view,
+                                       GList      *contacts);
+static void sw_contact_view_remove_contacts (SwContactView *contact_view,
+                                       GList      *contacts);
+
+static void
+sw_contact_view_get_property (GObject    *object,
+                           guint       property_id,
+                           GValue     *value,
+                           GParamSpec *pspec)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (object);
+
+  switch (property_id) {
+    case PROP_SERVICE:
+      g_value_set_object (value, priv->service);
+      break;
+    case PROP_OBJECT_PATH:
+      g_value_set_string (value, priv->object_path);
+      break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+sw_contact_view_set_property (GObject      *object,
+                           guint         property_id,
+                           const GValue *value,
+                           GParamSpec   *pspec)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (object);
+
+  switch (property_id) {
+    case PROP_SERVICE:
+      priv->service = g_value_dup_object (value);
+      break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+sw_contact_view_dispose (GObject *object)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (object);
+
+  if (priv->service)
+  {
+    g_object_unref (priv->service);
+    priv->service = NULL;
+  }
+
+  if (priv->current_contacts_set)
+  {
+    sw_set_unref (priv->current_contacts_set);
+    priv->current_contacts_set = NULL;
+  }
+
+  if (priv->pending_contacts_set)
+  {
+    sw_set_unref (priv->pending_contacts_set);
+    priv->pending_contacts_set = NULL;
+  }
+
+  if (priv->uid_to_contacts)
+  {
+    g_hash_table_unref (priv->uid_to_contacts);
+    priv->uid_to_contacts = NULL;
+  }
+
+  if (priv->pending_timeout_id)
+  {
+    g_source_remove (priv->pending_timeout_id);
+    priv->pending_timeout_id = 0;
+  }
+
+  if (priv->refresh_timeout_id)
+  {
+    g_source_remove (priv->refresh_timeout_id);
+    priv->refresh_timeout_id = 0;
+  }
+
+  G_OBJECT_CLASS (sw_contact_view_parent_class)->dispose (object);
+}
+
+static void
+sw_contact_view_finalize (GObject *object)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (object);
+
+  g_free (priv->object_path);
+
+  G_OBJECT_CLASS (sw_contact_view_parent_class)->finalize (object);
+}
+
+static gchar *
+_make_object_path (SwContactView *contact_view)
+{
+  gchar *path;
+  static gint count = 0;
+
+  path = g_strdup_printf ("/com/meego/libsocialweb/View%d",
+                          count);
+
+  count++;
+
+  return path;
+}
+
+static void
+sw_contact_view_constructed (GObject *object)
+{
+  SwContactView *contact_view = SW_CONTACT_VIEW (object);
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+  SwCore *core;
+
+  core = sw_core_dup_singleton ();
+
+  priv->object_path = _make_object_path (contact_view);
+  dbus_g_connection_register_g_object (sw_core_get_connection (core),
+                                       priv->object_path,
+                                       G_OBJECT (contact_view));
+  g_object_unref (core);
+  /* The only reference should be the one on the bus */
+
+  if (G_OBJECT_CLASS (sw_contact_view_parent_class)->constructed)
+    G_OBJECT_CLASS (sw_contact_view_parent_class)->constructed (object);
+}
+
+/* Default implementation for close */
+static void
+sw_contact_view_default_close (SwContactView *contact_view)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+  SwCore *core;
+
+  SW_DEBUG (VIEWS, "%s called on %s", G_STRFUNC, priv->object_path);
+
+  core = sw_core_dup_singleton ();
+  dbus_g_connection_unregister_g_object (sw_core_get_connection (core),
+                                         G_OBJECT (contact_view));
+  g_object_unref (core);
+
+  /* Object is no longer needed */
+  g_object_unref (contact_view);
+}
+
+static void
+sw_contact_view_class_init (SwContactViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (klass, sizeof (SwContactViewPrivate));
+
+  object_class->get_property = sw_contact_view_get_property;
+  object_class->set_property = sw_contact_view_set_property;
+  object_class->dispose = sw_contact_view_dispose;
+  object_class->finalize = sw_contact_view_finalize;
+  object_class->constructed = sw_contact_view_constructed;
+
+  klass->close = sw_contact_view_default_close;
+
+  pspec = g_param_spec_object ("service",
+                               "service",
+                               "The service this view is using",
+                               SW_TYPE_SERVICE,
+                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+  g_object_class_install_property (object_class, PROP_SERVICE, pspec);
+
+  pspec = g_param_spec_string ("object-path",
+                               "Object path",
+                               "The object path of this view",
+                               NULL,
+                               G_PARAM_READABLE);
+  g_object_class_install_property (object_class, PROP_OBJECT_PATH, pspec);
+}
+
+static void
+sw_contact_view_init (SwContactView *self)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (self);
+
+  priv->current_contacts_set = sw_contact_set_new ();
+  priv->pending_contacts_set = sw_contact_set_new ();
+
+  priv->uid_to_contacts = g_hash_table_new_full (g_str_hash,
+                                              g_str_equal,
+                                              g_free,
+                                              g_object_unref);
+}
+
+/* DBUS interface to class vfunc bindings */
+
+static void
+sw_contact_view_start (SwContactViewIface       *iface,
+                    DBusGMethodInvocation *context)
+{
+  SwContactView *contact_view = SW_CONTACT_VIEW (iface);
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  SW_DEBUG (VIEWS, "%s called on %s", G_STRFUNC, priv->object_path);
+
+  if (SW_CONTACT_VIEW_GET_CLASS (iface)->start)
+    SW_CONTACT_VIEW_GET_CLASS (iface)->start (contact_view);
+
+  sw_contact_view_iface_return_from_start (context);
+}
+
+static void
+sw_contact_view_refresh (SwContactViewIface       *iface,
+                      DBusGMethodInvocation *context)
+{
+  SwContactView *contact_view = SW_CONTACT_VIEW (iface);
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  SW_DEBUG (VIEWS, "%s called on %s", G_STRFUNC, priv->object_path);
+
+  if (SW_CONTACT_VIEW_GET_CLASS (iface)->refresh)
+    SW_CONTACT_VIEW_GET_CLASS (iface)->refresh (contact_view);
+
+  sw_contact_view_iface_return_from_refresh (context);
+}
+
+static void
+sw_contact_view_stop (SwContactViewIface       *iface,
+                   DBusGMethodInvocation *context)
+{
+  SwContactView *contact_view = SW_CONTACT_VIEW (iface);
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  SW_DEBUG (VIEWS, "%s called on %s", G_STRFUNC, priv->object_path);
+
+  if (SW_CONTACT_VIEW_GET_CLASS (iface)->stop)
+    SW_CONTACT_VIEW_GET_CLASS (iface)->stop (contact_view);
+
+  sw_contact_view_iface_return_from_stop (context);
+}
+
+static void
+sw_contact_view_close (SwContactViewIface       *iface,
+                    DBusGMethodInvocation *context)
+{
+  SwContactView *contact_view = SW_CONTACT_VIEW (iface);
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  SW_DEBUG (VIEWS, "%s called on %s", G_STRFUNC, priv->object_path);
+
+  if (SW_CONTACT_VIEW_GET_CLASS (iface)->close)
+    SW_CONTACT_VIEW_GET_CLASS (iface)->close (contact_view);
+
+  sw_contact_view_iface_return_from_close (context);
+}
+
+static void
+sw_contact_view_iface_init (gpointer g_iface,
+                         gpointer iface_data)
+{
+  SwContactViewIfaceClass *klass = (SwContactViewIfaceClass*)g_iface;
+  sw_contact_view_iface_implement_start (klass, sw_contact_view_start);
+  sw_contact_view_iface_implement_refresh (klass, sw_contact_view_refresh);
+  sw_contact_view_iface_implement_stop (klass, sw_contact_view_stop);
+  sw_contact_view_iface_implement_close (klass, sw_contact_view_close);
+}
+
+static gboolean
+_handle_ready_pending_cb (gpointer data)
+{
+  SwContactView *contact_view = SW_CONTACT_VIEW (data);
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+  GList *contacts_to_send = NULL;
+  GList *pending_contacts, *l;
+
+  SW_DEBUG (VIEWS, "Delayed ready timeout fired");
+
+  /* FIXME: Reword this to avoid unnecessary list creation ? */
+  pending_contacts = sw_set_as_list (priv->pending_contacts_set);
+
+  for (l = pending_contacts; l; l = l->next)
+  {
+    SwContact *contact = SW_CONTACT (l->data);
+
+    if (sw_contact_get_ready (contact))
+    {
+      contacts_to_send = g_list_prepend (contacts_to_send, contact);
+      sw_set_remove (priv->pending_contacts_set, (GObject *)contact);
+    }
+  }
+
+  sw_contact_view_add_contacts (contact_view, contacts_to_send);
+
+  g_list_free (pending_contacts);
+
+  priv->pending_timeout_id = 0;
+
+  return FALSE;
+}
+
+static void
+_contact_ready_weak_notify_cb (gpointer  data,
+                            GObject  *dead_object);
+
+static void
+_contact_ready_notify_cb (SwContact     *contact,
+                       GParamSpec *pspec,
+                       SwContactView *contact_view)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  if (sw_contact_get_ready (contact)) {
+    SW_DEBUG (VIEWS, "Contact became ready: %s.",
+              sw_contact_get (contact, "id"));
+    g_signal_handlers_disconnect_by_func (contact,
+                                          _contact_ready_notify_cb,
+                                          contact_view);
+    g_object_weak_unref ((GObject *)contact_view,
+                         _contact_ready_weak_notify_cb,
+                         contact);
+
+    if (!priv->pending_timeout_id)
+    {
+      SW_DEBUG (VIEWS, "Setting up timeout");
+      priv->pending_timeout_id = g_timeout_add_seconds (1,
+                                                        _handle_ready_pending_cb,
+                                                        contact_view);
+    } else {
+      SW_DEBUG (VIEWS, "Timeout already set up.");
+    }
+  }
+}
+
+static void
+_contact_ready_weak_notify_cb (gpointer  data,
+                            GObject  *dead_object)
+{
+  g_signal_handlers_disconnect_by_func (data,
+                                        _contact_ready_notify_cb,
+                                        dead_object);
+}
+
+static void
+_setup_ready_handler (SwContact     *contact,
+                      SwContactView *contact_view)
+{
+  g_signal_connect (contact,
+                    "notify::ready",
+                    (GCallback)_contact_ready_notify_cb,
+                    contact_view);
+  g_object_weak_ref ((GObject *)contact_view,
+                     _contact_ready_weak_notify_cb,
+                     contact);
+}
+
+static gboolean
+_contact_changed_timeout_cb (gpointer data)
+{
+  SwContactView *contact_view = SW_CONTACT_VIEW (data);
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  sw_contact_view_update_contacts (contact_view, priv->changed_contacts);
+  g_list_foreach (priv->changed_contacts, (GFunc)g_object_unref, NULL);
+  g_list_free (priv->changed_contacts);
+  priv->changed_contacts = NULL;
+
+  priv->refresh_timeout_id = 0;
+
+  return FALSE;
+}
+
+static void
+_contact_changed_cb (SwContact     *contact,
+                  SwContactView *contact_view)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  /* We only care if the contact is ready. If it's not then we don't want to be
+   * emitting changed but instead it will be added through the readiness
+   * tracking.
+   */
+  if (!sw_contact_get_ready (contact))
+    return;
+
+  if (!g_list_find (priv->changed_contacts, contact))
+    priv->changed_contacts = g_list_append (priv->changed_contacts, contact);
+
+  if (!priv->refresh_timeout_id)
+  {
+    SW_DEBUG (VIEWS, "Contact changed, Setting up timeout");
+
+    priv->refresh_timeout_id = g_timeout_add_seconds (10,
+                                                      _contact_changed_timeout_cb,
+                                                      contact_view);
+  }
+}
+
+static void
+_contact_changed_weak_notify_cb (gpointer  data,
+                              GObject  *dead_object)
+{
+  SwContact *contact = (SwContact *)data;
+
+  g_signal_handlers_disconnect_by_func (contact,
+                                        _contact_changed_cb,
+                                        dead_object);
+  g_object_unref (contact);
+}
+
+static void
+_setup_changed_handler (SwContact     *contact,
+                        SwContactView *contact_view)
+{
+  g_signal_connect (contact,
+                    "changed",
+                    (GCallback)_contact_changed_cb,
+                    contact_view);
+  g_object_weak_ref ((GObject *)contact_view,
+                     _contact_changed_weak_notify_cb,
+                     g_object_ref (contact));
+}
+
+/**
+ * sw_contact_view_add_contacts
+ * @contact_view: A #SwContactView
+ * @contacts: A list of #SwContact objects
+ *
+ * Add the contacts supplied in the list from the #SwContactView. In many
+ * cases what you actually want is sw_contact_view_remove_from_set() or
+ * sw_contact_view_set_from_set(). This will cause signal emissions over the
+ * bus.
+ *
+ * This is used in the implementation of sw_contact_view_remove_from_set()
+ */
+static void
+sw_contact_view_add_contacts (SwContactView *contact_view,
+                        GList      *contacts)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+  GValueArray *value_array;
+  GPtrArray *contacts_ptr_array;
+  GList *l;
+
+  contacts_ptr_array = g_ptr_array_new_with_free_func
+      ((GDestroyNotify)g_value_array_free);
+
+  for (l = contacts; l; l = l->next)
+  {
+    SwContact *contact = SW_CONTACT (l->data);
+
+    if (sw_contact_get_ready (contact))
+    {
+      SW_DEBUG (VIEWS, "Contact ready: %s",
+                sw_contact_get (contact, "id"));
+      value_array = _sw_contact_to_value_array (contact);
+      g_ptr_array_add (contacts_ptr_array, value_array);
+    } else {
+      SW_DEBUG (VIEWS, "Contact not ready, setting up handler: %s",
+                sw_contact_get (contact, "id"));
+      _setup_ready_handler (contact, contact_view);
+      sw_set_add (priv->pending_contacts_set, (GObject *)contact);
+    }
+
+    _setup_changed_handler (contact, contact_view);
+  }
+
+  SW_DEBUG (VIEWS, "Number of contacts to be added: %d", contacts_ptr_array->len);
+
+  if (contacts_ptr_array->len > 0)
+    sw_contact_view_iface_emit_contacts_added (contact_view,
+                                            contacts_ptr_array);
+
+  g_ptr_array_free (contacts_ptr_array, TRUE);
+}
+
+/**
+ * sw_contact_view_update_contacts
+ * @contact_view: A #SwContactView
+ * @contacts: A list of #SwContact objects that need updating
+ *
+ * Update the contacts supplied in the list in the #SwContactView. This is
+ * will cause signal emissions over the bus.
+ */
+static void
+sw_contact_view_update_contacts (SwContactView *contact_view,
+                           GList      *contacts)
+{
+  GValueArray *value_array;
+  GPtrArray *contacts_ptr_array;
+  GList *l;
+
+  contacts_ptr_array = g_ptr_array_new_with_free_func
+      ((GDestroyNotify)g_value_array_free);
+
+  for (l = contacts; l; l = l->next)
+  {
+    SwContact *contact = SW_CONTACT (l->data);
+
+    /*
+     * Contact must be ready and also not in the pending contacts set; we need to
+     * check this to prevent ContactsChanged coming before ContactsAdded
+     */
+    if (sw_contact_get_ready (contact))
+    {
+      value_array = _sw_contact_to_value_array (contact);
+      g_ptr_array_add (contacts_ptr_array, value_array);
+    }
+  }
+
+  SW_DEBUG (VIEWS, "Number of contacts to be changed: %d",
+      contacts_ptr_array->len);
+
+  if (contacts_ptr_array->len > 0)
+    sw_contact_view_iface_emit_contacts_changed (contact_view,
+                                           contacts_ptr_array);
+
+  g_ptr_array_free (contacts_ptr_array, TRUE);
+}
+
+/**
+ * sw_contact_view_remove_contacts
+ * @contact_view: A #SwContactView
+ * @contacts: A list of #SwContact objects
+ *
+ * Remove the contacts supplied in the list from the #SwContactView. In many
+ * cases what you actually want is sw_contact_view_remove_from_set() or
+ * sw_contact_view_set_from_set(). This will cause signal emissions over the
+ * bus.
+ *
+ * This is used in the implementation of sw_contact_view_remove_from_set()
+ *
+ */
+static void
+sw_contact_view_remove_contacts (SwContactView *contact_view,
+                           GList      *contacts)
+{
+  GValueArray *value_array;
+  GPtrArray *contacts_ptr_array;
+  GList *l;
+  SwContact *contact;
+
+  contacts_ptr_array = g_ptr_array_new_with_free_func
+      ((GDestroyNotify)g_value_array_free);
+
+  for (l = contacts; l; l = l->next)
+  {
+    int values_type = 0;
+    contact = SW_CONTACT (l->data);
+
+    value_array = g_value_array_new (2);
+
+    value_array = g_value_array_append (value_array, NULL);
+    g_value_init (g_value_array_get_nth (value_array, 0), G_TYPE_STRING);
+    g_value_set_string (g_value_array_get_nth (value_array, 0),
+                        sw_service_get_name (sw_contact_get_service (contact)));
+
+    value_array = g_value_array_append (value_array, NULL);
+    g_value_init (g_value_array_get_nth (value_array, 1), G_TYPE_STRING);
+    g_value_set_string (g_value_array_get_nth (value_array, 1),
+                        sw_contact_get (contact, "id"));
+
+    g_object_get (contact, "values-type", &values_type, NULL);
+    g_ptr_array_add (contacts_ptr_array, value_array);
+  }
+
+  if (contacts_ptr_array->len > 0)
+    sw_contact_view_iface_emit_contacts_removed (contact_view,
+                                           contacts_ptr_array);
+
+  g_ptr_array_free (contacts_ptr_array, TRUE);
+}
+
+/**
+ * sw_contact_view_get_object_path
+ * @contact_view: A #SwContactView
+ *
+ * Since #SwContactView is responsible for constructing the object path and
+ * registering the object on the bus. This function is necessary for
+ * #SwCore to be able to return the object path as the result of a
+ * function to open a view.
+ *
+ * Returns: A string providing the object path.
+ */
+const gchar *
+sw_contact_view_get_object_path (SwContactView *contact_view)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  return priv->object_path;
+}
+
+/**
+ * sw_contact_view_get_service
+ * @contact_view: A #SwContactView
+ *
+ * Returns: The #SwService that #SwContactView is for
+ */
+SwService *
+sw_contact_view_get_service (SwContactView *contact_view)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+
+  return priv->service;
+}
+
+/* TODO: Export this function ? */
+/**
+ * sw_contact_view_add_from_set
+ * @contact_view: A #SwContactView
+ * @set: A #SwSet
+ *
+ * Add the contacts that are in the supplied set to the view.
+ *
+ * This is used in the implementation of sw_contact_view_set_from_set()
+ */
+void
+sw_contact_view_add_from_set (SwContactView *contact_view,
+                           SwSet      *set)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+  GList *contacts;
+  GList *l;
+
+  sw_set_add_from (priv->current_contacts_set, set);
+  contacts = sw_set_as_list (set);
+
+  for (l = contacts; l; l = l->next)
+  {
+    SwContact *contact = (SwContact *)l->data;
+
+    g_hash_table_replace (priv->uid_to_contacts,
+                          g_strdup (sw_contact_get (contact, "id")),
+                          g_object_ref (contact));
+  }
+
+  sw_contact_view_add_contacts (contact_view, contacts);
+  g_list_free (contacts);
+}
+
+/* TODO: Export this function ? */
+/**
+ * sw_contact_view_remove_from_set
+ * @contact_view: A #SwContactView
+ * @set: A #SwSet
+ *
+ * Remove the contacts that are in the supplied set from the view.
+ *
+ * This is used in the implementation of sw_contact_view_set_from_set()
+ */
+void
+sw_contact_view_remove_from_set (SwContactView *contact_view,
+                              SwSet      *set)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+  GList *contacts;
+  GList *l;
+
+  sw_set_remove_from (priv->current_contacts_set, set);
+
+  contacts = sw_set_as_list (set);
+
+  for (l = contacts; l; l = l->next)
+  {
+    SwContact *contact = (SwContact *)l->data;
+
+    g_hash_table_remove (priv->uid_to_contacts,
+                         sw_contact_get (contact, "id"));
+  }
+
+  sw_contact_view_remove_contacts (contact_view, contacts);
+  g_list_free (contacts);
+}
+
+/**
+ * sw_contact_view_update_existing
+ * @contact_view: A #SwContactView
+ * @set: A #SwSet
+ *
+ * Replaces contacts in the internal set for the #SwContactView with the version
+ * from #SwSet if and only if they are sw_contact_equal() says that they are
+ * unequal. This prevents sending excessive contacts changed signals.
+ */
+static void
+sw_contact_view_update_existing (SwContactView *contact_view,
+                              SwSet      *set)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+  GList *contacts;
+  SwContact *new_contact;
+  SwContact *old_contact;
+  GList *l;
+  GList *contacts_to_send = NULL;
+
+  contacts = sw_set_as_list (set);
+
+  for (l = contacts; l; l = l->next)
+  {
+    new_contact = (SwContact *)l->data;
+    old_contact = g_hash_table_lookup (priv->uid_to_contacts,
+                                    sw_contact_get (new_contact, "id"));
+
+    /* This is just a new contact so we won't find it */
+    if (!old_contact)
+      continue;
+
+    if (!sw_contact_equal (new_contact, old_contact))
+    {
+      g_hash_table_replace (priv->uid_to_contacts,
+                            g_strdup (sw_contact_get (new_contact, "id")),
+                            new_contact);
+      /* 
+       * This works because sw_set_add uses g_hash_table_replace behind the
+       * scenes
+       */
+      sw_set_add (priv->current_contacts_set, (GObject *)new_contact);
+      contacts_to_send = g_list_append (contacts_to_send, g_object_ref (new_contact));
+    }
+  }
+
+  sw_contact_view_update_contacts (contact_view, contacts_to_send);
+
+  g_list_free (contacts);
+}
+
+/**
+ * sw_contact_view_set_from_set
+ * @contact_view: A #SwContactView
+ * @set: A #SwSet
+ *
+ * Updates what the view contains based on the given #SwSet. Removed
+ * signals will be fired for any contacts that were in the view but that are not
+ * present in the supplied set. Conversely any contacts that are new will cause
+ * signals to be fired indicating their addition.
+ *
+ * This implemented by maintaining a set inside the #SwContactView
+ */
+void
+sw_contact_view_set_from_set (SwContactView *contact_view,
+                           SwSet      *set)
+{
+  SwContactViewPrivate *priv = GET_PRIVATE (contact_view);
+  SwSet *added_contacts, *removed_contacts;
+
+  if (sw_set_is_empty (priv->current_contacts_set))
+  {
+    sw_contact_view_add_from_set (contact_view, set);
+  } else {
+    removed_contacts = sw_set_difference (priv->current_contacts_set, set);
+    added_contacts = sw_set_difference (set, priv->current_contacts_set);
+
+    if (!sw_set_is_empty (removed_contacts))
+      sw_contact_view_remove_from_set (contact_view, removed_contacts);
+
+    /* 
+     * Replace contacts that exist in the new set that are also present in the
+     * original set iff they're not equal
+     *
+     * This function will also cause the ContactsChanged signal to be fired with
+     * the contacts that have changed.
+     */
+    sw_contact_view_update_existing (contact_view, set);
+
+    if (!sw_set_is_empty (added_contacts))
+      sw_contact_view_add_from_set (contact_view, added_contacts);
+
+    sw_set_unref (removed_contacts);
+    sw_set_unref (added_contacts);
+  }
+}
diff --git a/libsocialweb/sw-contact-view.h b/libsocialweb/sw-contact-view.h
new file mode 100644
index 0000000..d5aeade
--- /dev/null
+++ b/libsocialweb/sw-contact-view.h
@@ -0,0 +1,71 @@
+/*
+ * libsocialweb - social data store
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * Author: Rob Bradford <rob linux intel com>
+ *         Alban Crequy <alban crequy collabora co uk>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef _SW_CONTACT_VIEW
+#define _SW_CONTACT_VIEW
+
+#include <glib-object.h>
+
+#include <libsocialweb/sw-contact.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_CONTACT_VIEW sw_contact_view_get_type()
+
+#define SW_CONTACT_VIEW(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_CONTACT_VIEW, SwContactView))
+
+#define SW_CONTACT_VIEW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_CONTACT_VIEW, SwContactViewClass))
+
+#define SW_IS_CONTACT_VIEW(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_CONTACT_VIEW))
+
+#define SW_IS_CONTACT_VIEW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_CONTACT_VIEW))
+
+#define SW_CONTACT_VIEW_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_CONTACT_VIEW, SwContactViewClass))
+
+typedef struct {
+  GObject parent;
+} SwContactView;
+
+typedef struct {
+  GObjectClass parent_class;
+  void (*start) (SwContactView *contact_view);
+  void (*refresh) (SwContactView *contact_view);
+  void (*stop) (SwContactView *contact_view);
+  void (*close) (SwContactView *contact_view);
+} SwContactViewClass;
+
+GType sw_contact_view_get_type (void);
+
+void sw_contact_view_set_from_set (SwContactView *contact_view,
+                                SwSet      *set);
+
+const gchar *sw_contact_view_get_object_path (SwContactView *contact_view);
+SwService *sw_contact_view_get_service (SwContactView *contact_view);
+
+G_END_DECLS
+
+#endif /* _SW_CONTACT_VIEW */
+
diff --git a/libsocialweb/sw-contact.c b/libsocialweb/sw-contact.c
new file mode 100644
index 0000000..061de25
--- /dev/null
+++ b/libsocialweb/sw-contact.c
@@ -0,0 +1,493 @@
+/*
+ * libsocialweb - social data store
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ * Copyright (C) 2011 Collabora Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <string.h>
+#include <libsocialweb/sw-utils.h>
+#include <libsocialweb/sw-web.h>
+#include "sw-contact.h"
+#include "sw-debug.h"
+
+G_DEFINE_TYPE (SwContact, sw_contact, G_TYPE_OBJECT)
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_CONTACT, SwContactPrivate))
+
+struct _SwContactPrivate {
+  /* TODO: fix lifecycle */
+  SwService *service;
+  /* Contact: hash (key: string) -> (GStrv value)
+   */
+  GHashTable *hash;
+  time_t cached_date;
+  time_t mtime;
+  gint remaining_fetches;
+};
+
+enum
+{
+  PROP_0,
+  PROP_READY,
+};
+
+enum
+{
+  CHANGED_SIGNAL,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = { 0, };
+
+static void
+sw_contact_dispose (GObject *object)
+{
+  SwContact *contact = SW_CONTACT (object);
+  SwContactPrivate *priv = contact->priv;
+
+  if (priv->hash) {
+    g_hash_table_unref (priv->hash);
+    priv->hash = NULL;
+  }
+
+  G_OBJECT_CLASS (sw_contact_parent_class)->dispose (object);
+}
+
+static void
+sw_contact_get_property (GObject    *object,
+                          guint       property_id,
+                          GValue     *value,
+                          GParamSpec *pspec)
+{
+  SwContact *contact = SW_CONTACT (object);
+
+  switch (property_id)
+  {
+    case PROP_READY:
+      g_value_set_boolean (value, sw_contact_get_ready (contact));
+      break;
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+sw_contact_set_property (GObject *object,
+                      guint property_id,
+                      const GValue *value,
+                      GParamSpec *pspec)
+{
+  switch (property_id)
+  {
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+  }
+}
+
+static void
+sw_contact_class_init (SwContactClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (klass, sizeof (SwContactPrivate));
+
+  object_class->dispose = sw_contact_dispose;
+  object_class->get_property = sw_contact_get_property;
+  object_class->set_property = sw_contact_set_property;
+
+  pspec = g_param_spec_boolean ("ready",
+                                "ready",
+                                "Whether contact is ready to set out",
+                                FALSE,
+                                G_PARAM_READABLE);
+  g_object_class_install_property (object_class, PROP_READY, pspec);
+
+  signals[CHANGED_SIGNAL] = g_signal_new ("changed",
+                                       SW_TYPE_CONTACT,
+                                       G_SIGNAL_RUN_FIRST,
+                                       G_STRUCT_OFFSET (SwContactClass, changed),
+                                       NULL,
+                                       NULL,
+                                       g_cclosure_marshal_VOID__VOID,
+                                       G_TYPE_NONE,
+                                       0);
+}
+
+static void
+sw_contact_init (SwContact *self)
+{
+  self->priv = GET_PRIVATE (self);
+  self->priv->hash = g_hash_table_new_full (NULL, NULL, NULL,
+      (GDestroyNotify) g_strfreev);
+
+}
+
+SwContact*
+sw_contact_new (void)
+{
+  return g_object_new (SW_TYPE_CONTACT, NULL);
+}
+
+void
+sw_contact_set_service (SwContact *contact, SwService *service)
+{
+  g_return_if_fail (SW_IS_CONTACT (contact));
+  g_return_if_fail (SW_IS_SERVICE (service));
+
+  /* TODO: weak reference? Remember to update dispose() */
+  contact->priv->service = service;
+}
+
+SwService *
+sw_contact_get_service (SwContact *contact)
+{
+  g_return_val_if_fail (SW_IS_CONTACT (contact), NULL);
+
+  return contact->priv->service;
+}
+
+void
+sw_contact_put (SwContact *contact, const char *key, const char *value)
+{
+  g_return_if_fail (SW_IS_CONTACT (contact));
+  g_return_if_fail (key);
+
+  GStrv str_array;
+  GStrv new_str_array;
+  str_array = g_hash_table_lookup (contact->priv->hash,
+                               (gpointer)g_intern_string (key));
+  if (str_array == NULL) {
+    new_str_array = g_new0 (gchar *, 2);
+    new_str_array[0] = g_strdup (value);
+  } else {
+    int i;
+    int len = g_strv_length (str_array);
+    new_str_array = g_new0 (gchar *, len + 2);
+    for (i = 0 ; i < len ; i++)
+      new_str_array[i] = g_strdup (str_array[i]);
+    new_str_array[len] = g_strdup (value);
+  }
+  g_hash_table_insert (contact->priv->hash,
+                       (gpointer)g_intern_string (key),
+                       new_str_array);
+
+  sw_contact_touch (contact);
+}
+
+void
+sw_contact_take (SwContact *contact, const char *key, char *value)
+{
+  g_return_if_fail (SW_IS_CONTACT (contact));
+  g_return_if_fail (key);
+
+  GStrv str_array;
+  GStrv new_str_array;
+  str_array = g_hash_table_lookup (contact->priv->hash,
+                               (gpointer)g_intern_string (key));
+  if (str_array == NULL) {
+    new_str_array = g_new0 (gchar *, 2);
+    new_str_array[0] = value;
+  } else {
+    int i;
+    int len = g_strv_length (str_array);
+    new_str_array = g_new0 (gchar *, len + 2);
+    for (i = 0 ; i < len ; i++)
+      new_str_array[i] = g_strdup (str_array[i]);
+    new_str_array[len] = value;
+  }
+  g_hash_table_insert (contact->priv->hash,
+                       (gpointer)g_intern_string (key),
+                       new_str_array);
+
+  sw_contact_touch (contact);
+}
+
+const char *
+sw_contact_get (const SwContact *contact, const char *key)
+{
+  g_return_val_if_fail (SW_IS_CONTACT (contact), NULL);
+  g_return_val_if_fail (key, NULL);
+
+  GStrv str_array = g_hash_table_lookup (contact->priv->hash,
+      g_intern_string (key));
+  if (!str_array)
+    return NULL;
+  return str_array[0];
+}
+
+static const GStrv
+sw_contact_get_all (const SwContact *contact, const char *key)
+{
+  g_return_val_if_fail (SW_IS_CONTACT (contact), NULL);
+  g_return_val_if_fail (key, NULL);
+
+  return g_hash_table_lookup (contact->priv->hash,
+        g_intern_string (key));
+}
+
+static void
+cache_date (SwContact *contact)
+{
+  const char *s;
+
+  if (contact->priv->cached_date)
+    return;
+
+  s = sw_contact_get (contact, "date");
+  if (!s)
+    return;
+
+  contact->priv->cached_date = sw_time_t_from_string (s);
+}
+
+void
+sw_contact_dump (SwContact *contact)
+{
+  GHashTableIter iter;
+  const char *key;
+  gpointer value;
+
+  g_return_if_fail (SW_IS_CONTACT (contact));
+
+  g_printerr ("SwContact %p\n", contact);
+  g_hash_table_iter_init (&iter, contact->priv->hash);
+  while (g_hash_table_iter_next (&iter,
+                                 (gpointer)&key,
+                                 &value)) {
+    gchar *concat = g_strjoinv (",", (GStrv) value);
+    g_printerr (" %s=%s\n", key, concat);
+    g_free (concat);
+  }
+}
+
+static guint
+contact_hash (gconstpointer key)
+{
+  const SwContact *contact = key;
+  return g_str_hash (sw_contact_get (contact, "id"));
+}
+
+gboolean
+contact_equal (gconstpointer a, gconstpointer b)
+{
+  const SwContact *contact_a = a;
+  const SwContact *contact_b = b;
+
+  return g_str_equal (sw_contact_get (contact_a, "id"),
+                      sw_contact_get (contact_b, "id"));
+}
+
+SwSet *
+sw_contact_set_new (void)
+{
+  return sw_set_new_full (contact_hash, contact_equal);
+}
+
+GHashTable *
+sw_contact_peek_hash (SwContact *contact)
+{
+  g_return_val_if_fail (SW_IS_CONTACT (contact), NULL);
+
+  return contact->priv->hash;
+}
+
+gboolean
+sw_contact_get_ready (SwContact *contact)
+{
+  return (contact->priv->remaining_fetches == 0);
+}
+
+void
+sw_contact_push_pending (SwContact *contact)
+{
+  g_atomic_int_inc (&(contact->priv->remaining_fetches));
+}
+
+void
+sw_contact_pop_pending (SwContact *contact)
+{
+  if (g_atomic_int_dec_and_test (&(contact->priv->remaining_fetches))) {
+    SW_DEBUG (CONTACT, "All outstanding fetches completed. Signalling ready: %s",
+                  sw_contact_get (contact, "id"));
+    g_object_notify (G_OBJECT (contact), "ready");
+  }
+
+  sw_contact_touch (contact);
+}
+
+
+typedef struct {
+  SwContact *contact;
+  const gchar *key;
+  gboolean delays_ready;
+} RequestImageFetchClosure;
+
+static void
+_image_download_cb (const char               *url,
+                    char                     *file,
+                    RequestImageFetchClosure *closure)
+{
+  SW_DEBUG (CONTACT, "Image fetched: %s to %s", url, file);
+  sw_contact_take (closure->contact,
+                    closure->key,
+                    file);
+
+  if (closure->delays_ready)
+    sw_contact_pop_pending (closure->contact);
+
+  g_object_unref (closure->contact);
+  g_slice_free (RequestImageFetchClosure, closure);
+}
+
+void
+sw_contact_request_image_fetch (SwContact      *contact,
+                             gboolean     delays_ready,
+                             const gchar *key,
+                             const gchar *url)
+{
+  RequestImageFetchClosure *closure;
+
+  /* If this URL fetch should delay the contact being considered ready, or
+   * whether the contact is useful without this key.
+   */
+  if (delays_ready)
+    sw_contact_push_pending (contact);
+
+  closure = g_slice_new0 (RequestImageFetchClosure);
+
+  closure->key = g_intern_string (key);
+  closure->contact = g_object_ref (contact);
+  closure->delays_ready = delays_ready;
+
+  SW_DEBUG (CONTACT, "Scheduling fetch for %s on: %s",
+            url,
+            sw_contact_get (closure->contact, "id"));
+  sw_web_download_image_async (url,
+                               (ImageDownloadCallback)_image_download_cb,
+                               closure);
+}
+
+/*
+ * Construct a GValueArray from a SwContact. We use this to construct the
+ * data types that the wonderful dbus-glib needs to emit the signal
+ */
+GValueArray *
+_sw_contact_to_value_array (SwContact *contact)
+{
+  GValueArray *value_array;
+  time_t time;
+
+  time = sw_time_t_from_string (sw_contact_get (contact, "date"));
+
+  value_array = g_value_array_new (4);
+
+  value_array = g_value_array_append (value_array, NULL);
+  g_value_init (g_value_array_get_nth (value_array, 0), G_TYPE_STRING);
+  g_value_set_string (g_value_array_get_nth (value_array, 0),
+                      sw_service_get_name (sw_contact_get_service (contact)));
+
+  value_array = g_value_array_append (value_array, NULL);
+  g_value_init (g_value_array_get_nth (value_array, 1), G_TYPE_STRING);
+  g_value_set_string (g_value_array_get_nth (value_array, 1),
+                      sw_contact_get (contact, "id"));
+
+  value_array = g_value_array_append (value_array, NULL);
+  g_value_init (g_value_array_get_nth (value_array, 2), G_TYPE_INT64);
+  g_value_set_int64 (g_value_array_get_nth (value_array, 2),
+                     time);
+
+  value_array = g_value_array_append (value_array, NULL);
+  g_value_init (g_value_array_get_nth (value_array, 3),
+                dbus_g_type_get_map ("GHashTable",
+                  G_TYPE_STRING,
+                  G_TYPE_STRV));
+
+  g_value_set_boxed (g_value_array_get_nth (value_array, 3),
+                     sw_contact_peek_hash (contact));
+
+  return value_array;
+}
+
+void
+sw_contact_touch (SwContact *contact)
+{
+  contact->priv->mtime = time (NULL);
+
+  g_signal_emit (contact, signals[CHANGED_SIGNAL], 0);
+}
+
+time_t
+sw_contact_get_mtime (SwContact *contact)
+{
+  return contact->priv->mtime;
+}
+
+/* Intentionally don't compare the mtime */
+gboolean
+sw_contact_equal (SwContact *a,
+               SwContact *b)
+{
+  SwContactPrivate *priv_a = GET_PRIVATE (a);
+  SwContactPrivate *priv_b = GET_PRIVATE (b);
+  GHashTable *hash_a = priv_a->hash;
+  GHashTable *hash_b = priv_b->hash;
+  GHashTableIter iter_a;
+  gpointer key_a, value_a;
+  guint size_a, size_b;
+
+  if (priv_a->service != priv_b->service)
+    return FALSE;
+
+  if (priv_a->remaining_fetches != priv_b->remaining_fetches)
+    return FALSE;
+
+  size_a = g_hash_table_size (hash_a);
+  size_b = g_hash_table_size (hash_b);
+
+  if (sw_contact_get (a, "cached"))
+    size_a--;
+
+  if (sw_contact_get (b, "cached"))
+    size_b--;
+
+  if (size_a != size_b)
+    return FALSE;
+
+  g_hash_table_iter_init (&iter_a, hash_a);
+
+  while (g_hash_table_iter_next (&iter_a, &key_a, &value_a)) 
+  {
+    if (g_str_equal (key_a, "cached"))
+      continue;
+
+    GStrv value_b;
+    int i;
+    value_b = sw_contact_get_all (b, key_a);
+    if (g_strv_length (value_a) != g_strv_length (value_b))
+      return FALSE;
+
+    for (i = 0 ; i < g_strv_length (value_a) ; i++) {
+      if (!g_str_equal (((GStrv)value_a)[i], value_b[i]))
+        return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
diff --git a/libsocialweb/sw-contact.h b/libsocialweb/sw-contact.h
new file mode 100644
index 0000000..ea5cde7
--- /dev/null
+++ b/libsocialweb/sw-contact.h
@@ -0,0 +1,105 @@
+/*
+ * libsocialweb - social data store
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU Lesser General Public License,
+ * version 2.1, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef _SW_CONTACT
+#define _SW_CONTACT
+
+#include <glib-object.h>
+#include <libsocialweb/sw-types.h>
+#include <libsocialweb/sw-service.h>
+#include <libsocialweb/sw-set.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_CONTACT sw_contact_get_type()
+
+#define SW_CONTACT(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_CONTACT, SwContact))
+
+#define SW_CONTACT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_CONTACT, SwContactClass))
+
+#define SW_IS_CONTACT(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_CONTACT))
+
+#define SW_IS_CONTACT_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_CONTACT))
+
+#define SW_CONTACT_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_CONTACT, SwContactClass))
+
+typedef struct _SwContactPrivate SwContactPrivate;
+
+struct _SwContact {
+  GObject parent;
+  SwContactPrivate *priv;
+};
+
+typedef struct {
+  GObjectClass parent_class;
+  void (*changed)(SwContact *contact);
+} SwContactClass;
+
+GType sw_contact_get_type (void);
+
+SwContact* sw_contact_new (void);
+
+void sw_contact_set_service (SwContact *contact, SwService *service);
+
+SwService * sw_contact_get_service (SwContact *contact);
+
+void sw_contact_put (SwContact     *contact,
+                  const char *key,
+                  const char *value);
+
+void sw_contact_take (SwContact     *contact,
+                   const char *key,
+                   char       *value);
+
+void sw_contact_request_image_fetch (SwContact      *contact,
+                                  gboolean     delays_ready,
+                                  const gchar *key,
+                                  const gchar *url);
+
+const char * sw_contact_get (const SwContact *contact, const char *key);
+
+void sw_contact_dump (SwContact *contact);
+
+GHashTable *sw_contact_peek_hash (SwContact *contact);
+
+gboolean sw_contact_get_ready (SwContact *contact);
+
+void sw_contact_push_pending (SwContact *contact);
+void sw_contact_pop_pending (SwContact *contact);
+
+void sw_contact_touch (SwContact *contact);
+time_t sw_contact_get_mtime (SwContact *contact);
+
+
+gboolean sw_contact_equal (SwContact *a,
+                        SwContact *b);
+
+/* Convenience function */
+SwSet *sw_contact_set_new (void);
+
+/* Useful for emitting the signals */
+GValueArray *_sw_contact_to_value_array (SwContact *contact);
+
+G_END_DECLS
+
+#endif /* _SW_CONTACT */
diff --git a/libsocialweb/sw-debug.c b/libsocialweb/sw-debug.c
index 365ac46..cf2e57f 100644
--- a/libsocialweb/sw-debug.c
+++ b/libsocialweb/sw-debug.c
@@ -32,6 +32,7 @@ sw_debug_init (const char *string)
     { "views", SW_DEBUG_VIEWS },
     { "online", SW_DEBUG_ONLINE },
     { "item", SW_DEBUG_ITEM },
+    { "contact", SW_DEBUG_CONTACT },
     { "twitter", SW_DEBUG_TWITTER },
     { "lastfm", SW_DEBUG_LASTFM },
     { "core", SW_DEBUG_CORE },
diff --git a/libsocialweb/sw-debug.h b/libsocialweb/sw-debug.h
index 7e5eedf..68c37aa 100644
--- a/libsocialweb/sw-debug.h
+++ b/libsocialweb/sw-debug.h
@@ -23,15 +23,16 @@ typedef enum {
   SW_DEBUG_VIEWS = 1 << 1,
   SW_DEBUG_ONLINE = 1 << 2,
   SW_DEBUG_ITEM = 1 << 3,
-  SW_DEBUG_TWITTER = 1 << 4,
-  SW_DEBUG_LASTFM = 1 << 5,
-  SW_DEBUG_CORE = 1 << 6,
-  SW_DEBUG_VIMEO = 1 << 7,
-  SW_DEBUG_FLICKR = 1 << 8,
-  SW_DEBUG_SMUGMUG = 1 << 9,
-  SW_DEBUG_PHOTOBUCKET = 1 << 10,
-  SW_DEBUG_FACEBOOK = 1 << 11,
-  SW_DEBUG_CLIENT_MONITOR = 1 << 12
+  SW_DEBUG_CONTACT = 1 << 4,
+  SW_DEBUG_TWITTER = 1 << 5,
+  SW_DEBUG_LASTFM = 1 << 6,
+  SW_DEBUG_CORE = 1 << 7,
+  SW_DEBUG_VIMEO = 1 << 8,
+  SW_DEBUG_FLICKR = 1 << 9,
+  SW_DEBUG_SMUGMUG = 1 << 10,
+  SW_DEBUG_PHOTOBUCKET = 1 << 11,
+  SW_DEBUG_FACEBOOK = 1 << 12,
+  SW_DEBUG_CLIENT_MONITOR = 1 << 13
 } SwDebugFlags;
 
 extern guint sw_debug_flags;
diff --git a/libsocialweb/sw-types.h b/libsocialweb/sw-types.h
index 38fbcc0..cb52c41 100644
--- a/libsocialweb/sw-types.h
+++ b/libsocialweb/sw-types.h
@@ -29,6 +29,8 @@ typedef struct _SwSet SwSet;
 
 typedef struct _SwItem SwItem;
 
+typedef struct _SwContact SwContact;
+
 typedef struct _SwCore SwCore;
 
 G_END_DECLS



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]