[libsocialweb: 1/9] facebook: Added Facebook service.



commit 92758a77d3a0182c79db7829b1a59150695e0560
Author: Eitan Isaacson <eitan isaacson collabora co uk>
Date:   Mon Jan 31 13:24:58 2011 +0200

    facebook: Added Facebook service.

 configure.ac                           |    2 +
 services/Makefile.am                   |    4 +
 services/facebook/Makefile.am          |   32 ++
 services/facebook/facebook-item-view.c |  646 +++++++++++++++++++++++++++
 services/facebook/facebook-item-view.h |   60 +++
 services/facebook/facebook-util.c      |  171 +++++++
 services/facebook/facebook-util.h      |   40 ++
 services/facebook/facebook.c           |  756 ++++++++++++++++++++++++++++++++
 services/facebook/facebook.h           |   63 +++
 services/facebook/facebook.keys.in     |   11 +
 services/facebook/module.c             |   35 ++
 11 files changed, 1820 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 70d1a21..796baeb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -125,6 +125,7 @@ AS_IF(
 AC_ARG_ENABLE([all-services],[AS_HELP_STRING([--enable-all-services], [enable every service])],
               [], [enable_all_services=no])
 
+SOCIALWEB_ENABLE_SERVICE(Facebook, facebook, FACEBOOK)
 SOCIALWEB_ENABLE_SERVICE(Flickr, flickr, FLICKR)
 SOCIALWEB_ENABLE_SERVICE(Last.fm, lastfm, LASTFM)
 SOCIALWEB_ENABLE_SERVICE(Twitter, twitter, TWITTER)
@@ -180,6 +181,7 @@ AC_OUTPUT([
         src/Makefile
         services/Makefile
         services/dummy/Makefile
+        services/facebook/Makefile
         services/flickr/Makefile
         services/lastfm/Makefile
         services/twitter/Makefile
diff --git a/services/Makefile.am b/services/Makefile.am
index 722337a..d049379 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -1,5 +1,9 @@
 SUBDIRS = dummy
 
+if WITH_FACEBOOK
+SUBDIRS += facebook
+endif
+
 if WITH_FLICKR
 SUBDIRS += flickr
 endif
diff --git a/services/facebook/Makefile.am b/services/facebook/Makefile.am
new file mode 100644
index 0000000..26b7fd8
--- /dev/null
+++ b/services/facebook/Makefile.am
@@ -0,0 +1,32 @@
+services_LTLIBRARIES = libfacebook.la
+
+libfacebook_la_SOURCES = \
+	module.c \
+	facebook.c facebook.h \
+	facebook-util.c facebook-util.h \
+	facebook-item-view.c facebook-item-view.h
+
+libfacebook_la_CFLAGS = \
+	-I$(top_srcdir) \
+	$(REST_CFLAGS) \
+	$(JSON_GLIB_CFLAGS) \
+	$(DBUS_GLIB_CFLAGS) \
+	-DG_LOG_DOMAIN=\"Facebook\"
+
+libfacebook_la_LIBADD = \
+	$(top_builddir)/libsocialweb/libsocialweb.la \
+	$(top_builddir)/libsocialweb-keyfob/libsocialweb-keyfob.la \
+	$(top_builddir)/libsocialweb-keystore/libsocialweb-keystore.la \
+	$(REST_LIBS) \
+	$(JSON_GLIB_LIBS) \
+	$(DBUS_GLIB_CFLAGS)
+
+libfacebook_la_LDFLAGS = \
+	-module \
+	-avoid-version
+
+servicesdata_DATA = facebook.keys
+ INTLTOOL_SOCIALWEB_KEYS@
+
+CLEANFILES = facebook.keys
+EXTRA_DIST = facebook.keys.in
diff --git a/services/facebook/facebook-item-view.c b/services/facebook/facebook-item-view.c
new file mode 100644
index 0000000..778e31b
--- /dev/null
+++ b/services/facebook/facebook-item-view.c
@@ -0,0 +1,646 @@
+/*
+ * libsocialweb Facebook service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ * Copyright (C) Collabora Ltd.
+ *
+ * Authors: Gary Ching-Pang Lin <glin novell com>
+ *          Thomas Thurman <thomas thurman collabora co uk>
+ *          Jonathon Jongsma <jonathon jongsma collabora co uk>
+ *          Danielle Madeley <danielle madeley 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 "facebook-item-view.h"
+#include "facebook.h"
+#include "facebook-util.h"
+
+#include <libsocialweb/sw-item.h>
+#include <libsocialweb/sw-set.h>
+#include <libsocialweb/sw-cache.h>
+
+#include <rest/rest-proxy.h>
+#include <rest/rest-xml-parser.h>
+#include <json-glib/json-glib.h>
+
+#define GET_PRIVATE(o) (((SwFacebookItemView *) o)->priv)
+
+#define UPDATE_TIMEOUT (5*60)
+
+G_DEFINE_TYPE (SwFacebookItemView, sw_facebook_item_view, SW_TYPE_ITEM_VIEW);
+
+static void facebook_item_view_stop (SwItemView *self);
+static void facebook_item_view_refresh (SwItemView *self);
+
+struct _SwFacebookItemViewPrivate
+{
+  RestProxy *proxy;
+  gchar *query;
+  GHashTable *params;
+
+  guint running;
+};
+
+enum /* properties */
+{
+  PROP_0,
+  PROP_PROXY,
+  PROP_QUERY,
+  PROP_PARAMS
+};
+
+static char*
+_facebook_status_node_get_link (JsonNode *status_node)
+{
+  JsonObject *status_object = json_node_get_object (status_node);
+  char *url = get_child_node_value (status_node, "link");
+
+  if (url == NULL)
+    {
+      /* try to extract a link to the 'comment' action for this post, which
+       * serves as a 'permalink' for a particular status update */
+      JsonArray *actions = NULL;
+      JsonNode *actions_node = json_object_get_member (status_object,
+                                                       "actions");
+
+      if (actions_node != NULL)
+        actions = json_node_get_array (actions_node);
+
+      if (actions != NULL)
+        {
+          guint j;
+
+          for (j = 0; j < json_array_get_length (actions); j++)
+            {
+              JsonNode *action = json_array_get_element (actions, j);
+              char *action_name;
+
+              action_name = get_child_node_value (action, "name");
+
+              if (action_name == NULL)
+                {
+                  continue;
+                }
+              else if (g_ascii_strcasecmp (action_name, "Comment") != 0)
+                {
+                  g_free (action_name);
+
+                  continue;
+                }
+
+              g_free (action_name);
+
+              url = get_child_node_value (action, "link");
+
+              break;
+            }
+          }
+    }
+
+  if (url == NULL)
+    {
+      /* can't find a decent url to associate with this post, so just link to
+       * the facebook homepage */
+      url = g_strdup ("http://www.facebook.com";);
+    }
+
+  return url;
+}
+
+static SwItem*
+_facebook_status_node_to_item (SwItemView *self,
+                               JsonNode   *status_node)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+  SwItem *item;
+  char *id, *uid, *post_time, *message, *pic_url;
+  char *name = NULL, *authorid = NULL;
+  char *thumbnail = NULL;
+  char *url = NULL;
+  char *post_type = NULL;
+  const char *my_uid = sw_service_facebook_get_uid (
+      (SwServiceFacebook *) sw_item_view_get_service (self));
+  JsonObject *status_object;
+  JsonNode *from, *to;
+
+  if (!json_node_get_node_type (status_node) == JSON_NODE_OBJECT)
+    return NULL;
+
+  status_object = json_node_get_object (status_node);
+
+  /* We only support status messages at the moment.  In the future, we may add
+   * support for photos, links, etc */
+  post_type = get_child_node_value (status_node, "type");
+  if (g_strcmp0 (post_type, "status") != 0)
+    {
+      g_free (post_type);
+
+      return NULL;
+    }
+
+  g_free (post_type);
+
+  /* In addition, we don't yet support messages that are targetted at a
+   * particular user (unless that user is you).  Without any context about whose
+   * wall the message was written on, these user-to-user messages become rather
+   * confusing. So before we support them, we would need to introduce a concept
+   * of a 'target' user to SwItem or something similar */
+  to = json_object_get_member (status_object, "to");
+
+  if (to != NULL)
+    {
+      JsonObject *to_obj = NULL;
+      JsonArray *to_array = NULL;
+      guint i;
+      gboolean to_me = FALSE;
+
+      to_obj = json_node_get_object (to);
+      to_array = json_object_get_array_member (to_obj, "data");
+
+      for (i = 0; i < json_array_get_length (to_array); i++)
+        {
+          JsonNode *user;
+
+          user = json_array_get_element (to_array, i);
+
+          if (user != NULL)
+            {
+              char *to_id = get_child_node_value (user, "id");
+
+              if (to_id != NULL && g_strcmp0 (my_uid, to_id) == 0)
+                {
+                  to_me = TRUE;
+
+                  g_free (to_id);
+
+                  break;
+                }
+
+              g_free (to_id);
+            }
+        }
+
+      if (!to_me)
+        return NULL;
+    }
+
+  item = sw_item_new ();
+  sw_item_set_service (item, sw_item_view_get_service (self));
+
+  /* we use created_time here so that items don't keep getting pushed up to
+   * the top of the list when people comment on them, etc.  If and when we
+   * implement support for comments on items, we could revisit that decision
+   */
+  post_time = get_child_node_value (status_node, "created_time");
+  if (post_time == NULL)
+    {
+      g_debug ("Got status update without a date");
+      g_object_unref (item);
+
+      return NULL;
+    }
+
+  sw_item_take (item, "date", post_time);
+
+  /* Construct item ID */
+  uid = get_child_node_value (status_node, "id");
+  if (uid == NULL)
+    {
+      g_debug ("Got status update without an id");
+      g_object_unref (item);
+
+      return NULL;
+    }
+
+  id = g_strconcat ("facebook-", uid, NULL);
+  g_free (uid);
+  sw_item_take (item, "id", id);
+
+  message = get_child_node_value (status_node, "message");
+  if (message == NULL || message[0] == '\0')
+    {
+      g_debug ("Got status update without a message");
+      g_free (message);
+      g_object_unref (item);
+
+      return NULL;
+  }
+  sw_item_take (item, "content", message);
+
+  from = json_object_get_member (status_object, "from");
+  if (from != NULL)
+    {
+      name = get_child_node_value (from, "name");
+      authorid = get_child_node_value (from, "id");
+      sw_item_take (item, "authorid", authorid);
+    }
+
+  if (name == NULL)
+    {
+      g_debug ("Got status update without an author name");
+      g_object_unref (item);
+
+      return NULL;
+  }
+  sw_item_take (item, "author", name);
+
+  if (authorid != NULL)
+    {
+      pic_url = build_picture_url (priv->proxy, authorid,
+                                   FB_PICTURE_SIZE_SQUARE);
+      sw_item_request_image_fetch (item, FALSE, "authoricon", pic_url);
+      g_free (pic_url);
+    }
+
+  /* thumbnail is not likely to exist on a status update, but just in case */
+  thumbnail = get_child_node_value (status_node, "picture");
+  if (thumbnail != NULL)
+    {
+      sw_item_request_image_fetch (item, FALSE, "thumbnail", thumbnail);
+      g_free (thumbnail);
+    }
+
+  url = _facebook_status_node_get_link (status_node);
+  if (url != NULL)
+    sw_item_take (item, "url", url);
+
+  return item;
+}
+
+static SwSet*
+_facebook_status_node_to_set (SwItemView *self,
+                              JsonNode   *root)
+{
+  JsonObject *root_object = NULL;
+  JsonNode *statuses = NULL;
+  JsonArray *status_array = NULL;
+  SwSet *set = NULL;
+  guint i;
+
+  if (json_node_get_node_type (root) == JSON_NODE_OBJECT)
+    root_object = json_node_get_object (root);
+  else
+    return NULL;
+
+  if (!json_object_has_member (root_object, "data"))
+    return NULL;
+
+  statuses = json_object_get_member (root_object, "data");
+
+  if (json_node_get_node_type (statuses) == JSON_NODE_ARRAY)
+    status_array = json_node_get_array (statuses);
+  else
+    return NULL;
+
+  set = sw_item_set_new ();
+
+  for (i = 0; i < json_array_get_length (status_array); i++)
+    {
+      JsonNode *status;
+      status = json_array_get_element (status_array, i);
+
+      SwItem *item = _facebook_status_node_to_item (self, status);
+
+      if (item != NULL)
+        {
+          sw_set_add (set, G_OBJECT (item));
+          g_object_unref (item);
+        }
+    }
+
+  return set;
+}
+
+static void
+got_status_cb (RestProxyCall *call,
+               GError        *error,
+               GObject       *weak_object,
+               gpointer       userdata)
+{
+  SwItemView *self = SW_ITEM_VIEW (weak_object);
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+  JsonNode *root;
+  SwSet *set;
+
+  if (error)
+    {
+      g_message ("Error: %s", error->message);
+
+      return;
+    }
+
+  root = json_node_from_call (call, NULL);
+  if (!root)
+    return;
+
+  set = _facebook_status_node_to_set (self, root);
+
+  json_node_free (root);
+
+  if (set != NULL)
+    {
+      sw_item_view_set_from_set (self, set);
+
+      /* Save the results of this set to the cache */
+      sw_cache_save (sw_item_view_get_service (self),
+                     priv->query,
+                     priv->params,
+                     set);
+
+      sw_set_unref (set);
+    }
+}
+
+static void
+get_status_updates (SwItemView *self)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+  RestProxyCall *call;
+  const char *my_uid = sw_service_facebook_get_uid (
+      (SwServiceFacebook *) sw_item_view_get_service (self));
+
+  if (my_uid == NULL || priv->running == 0)
+    return;
+
+  call = rest_proxy_new_call (priv->proxy);
+
+  if (g_strcmp0 (priv->query, "own") == 0)
+    rest_proxy_call_set_function (call, "me/feed");
+  else if (g_strcmp0 (priv->query, "feed") == 0 ||
+           g_strcmp0 (priv->query, "friends-only") == 0)
+    rest_proxy_call_set_function (call, "me/home");
+  else
+    g_return_if_reached ();
+
+  rest_proxy_call_async (call,
+                         (RestProxyCallAsyncCallback) got_status_cb,
+                         (GObject*) self,
+                         NULL,
+                         NULL);
+
+  g_object_unref (call);
+}
+
+static void
+load_from_cache (SwItemView *self)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+  SwSet *set;
+
+  set = sw_cache_load (sw_item_view_get_service (self),
+                       priv->query,
+                       priv->params);
+
+  if (set != NULL)
+    {
+      sw_item_view_set_from_set (self, set);
+
+      sw_set_unref (set);
+    }
+}
+
+static void
+_service_item_hidden (SwService   *service,
+                      const gchar *uid,
+                      SwItemView  *self)
+{
+  sw_item_view_remove_by_uid (self, uid);
+}
+
+static void
+_service_user_changed (SwService  *service,
+                       SwItemView *self)
+{
+  SwSet *set;
+
+  /* We need to empty the set */
+  set = sw_item_set_new ();
+  sw_item_view_set_from_set (self, set);
+  sw_set_unref (set);
+
+  /* And drop the cache */
+  sw_cache_drop_all (service);
+}
+
+static void
+_service_capabilities_changed (SwService    *service,
+                               const gchar **caps,
+                               SwItemView   *self)
+{
+  if (sw_service_has_cap (caps, CREDENTIALS_VALID))
+  {
+    facebook_item_view_refresh (self);
+  }
+}
+
+static void
+facebook_item_view_constructed (GObject *self)
+{
+  SwService *service = sw_item_view_get_service ((SwItemView *) self);
+
+  g_signal_connect_object (service, "item-hidden",
+                    G_CALLBACK (_service_item_hidden), self, 0);
+  g_signal_connect_object (service, "user-changed",
+                    G_CALLBACK (_service_user_changed), self, 0);
+  g_signal_connect_object (service, "capabilities-changed",
+                    G_CALLBACK (_service_capabilities_changed), self, 0);
+
+  if (G_OBJECT_CLASS (sw_facebook_item_view_parent_class)->constructed != NULL)
+    G_OBJECT_CLASS (sw_facebook_item_view_parent_class)->constructed (self);
+}
+
+static void
+facebook_item_view_set_property (GObject      *self,
+                                 guint         property_id,
+                                 const GValue *value,
+                                 GParamSpec   *pspec)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+
+  switch (property_id)
+    {
+      case PROP_PROXY:
+        priv->proxy = g_value_dup_object (value);
+        break;
+
+      case PROP_QUERY:
+        priv->query = g_value_dup_string (value);
+        break;
+
+      case PROP_PARAMS:
+        priv->params = g_value_dup_boxed (value);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+        break;
+    }
+}
+
+static void
+facebook_item_view_get_property (GObject    *self,
+                                 guint       property_id,
+                                 GValue     *value,
+                                 GParamSpec *pspec)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+
+  switch (property_id)
+    {
+      case PROP_PROXY:
+        g_value_set_object (value, priv->proxy);
+        break;
+
+      case PROP_QUERY:
+        g_value_set_string (value, priv->query);
+        break;
+
+      case PROP_PARAMS:
+        g_value_set_boxed (value, priv->params);
+        break;
+
+      default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (self, property_id, pspec);
+        break;
+    }
+}
+
+static void
+facebook_item_view_dispose (GObject *self)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+
+  facebook_item_view_stop ((SwItemView *) self);
+
+  g_object_unref (priv->proxy);
+  priv->proxy = NULL;
+
+  G_OBJECT_CLASS (sw_facebook_item_view_parent_class)->dispose (self);
+}
+
+static void
+facebook_item_view_finalize (GObject *self)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+
+  g_free (priv->query);
+  g_boxed_free (G_TYPE_HASH_TABLE, priv->params);
+
+  G_OBJECT_CLASS (sw_facebook_item_view_parent_class)->finalize (self);
+}
+
+static gboolean
+_update_timeout_cb (gpointer user_data)
+{
+   get_status_updates ((SwItemView *) user_data);
+
+   return TRUE;
+}
+
+static void
+facebook_item_view_start (SwItemView *self)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+
+  if (priv->running != 0)
+    {
+      g_message (G_STRLOC ": View asked to start, but already running");
+    }
+  else
+    {
+      g_debug ("Starting up the Facebook view");
+
+      priv->running = g_timeout_add_seconds (UPDATE_TIMEOUT,
+                                             _update_timeout_cb,
+                                             self);
+
+      load_from_cache (self);
+      get_status_updates (self);
+    }
+}
+
+static void
+facebook_item_view_stop (SwItemView *self)
+{
+  SwFacebookItemViewPrivate *priv = GET_PRIVATE (self);
+
+  if (priv->running == 0)
+    {
+      g_message (G_STRLOC ": View ask to stop, but not running");
+    }
+  else
+    {
+      g_debug ("Stopping the Facebook view");
+
+      g_source_remove (priv->running);
+      priv->running = 0;
+    }
+
+}
+
+static void
+facebook_item_view_refresh (SwItemView *self)
+{
+  g_debug ("Forced a refresh of the Facebook view");
+
+  get_status_updates (self);
+}
+
+static void
+sw_facebook_item_view_class_init (SwFacebookItemViewClass *klass)
+{
+  GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+  SwItemViewClass *item_view_class = SW_ITEM_VIEW_CLASS (klass);
+
+  gobject_class->constructed = facebook_item_view_constructed;
+  gobject_class->set_property = facebook_item_view_set_property;
+  gobject_class->get_property = facebook_item_view_get_property;
+  gobject_class->dispose = facebook_item_view_dispose;
+  gobject_class->finalize = facebook_item_view_finalize;
+
+  item_view_class->start = facebook_item_view_start;
+  item_view_class->stop = facebook_item_view_stop;
+  item_view_class->refresh = facebook_item_view_refresh;
+
+  g_object_class_install_property (gobject_class, PROP_PROXY,
+      g_param_spec_object ("proxy",
+        "Proxy",
+        "The #RestProxy we're using to make API calls",
+        REST_TYPE_PROXY,
+        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_QUERY,
+      g_param_spec_string ("query",
+        "Query",
+        "The query requested for this view",
+        NULL,
+        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_PARAMS,
+      g_param_spec_boxed ("params",
+        "Params",
+        "Additional parameters passed to the query",
+        G_TYPE_HASH_TABLE,
+        G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_type_class_add_private (gobject_class, sizeof (SwFacebookItemViewPrivate));
+}
+
+static void
+sw_facebook_item_view_init (SwFacebookItemView *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self),
+      SW_TYPE_FACEBOOK_ITEM_VIEW, SwFacebookItemViewPrivate);
+}
diff --git a/services/facebook/facebook-item-view.h b/services/facebook/facebook-item-view.h
new file mode 100644
index 0000000..f2a573b
--- /dev/null
+++ b/services/facebook/facebook-item-view.h
@@ -0,0 +1,60 @@
+/*
+ * libsocialweb Facebook service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ * Copyright (C) 2010 Collabora Ltd.
+ *
+ * Authors: Gary Ching-Pang Lin <glin novell com>
+ *          Thomas Thurman <thomas thurman collabora co uk>
+ *          Jonathon Jongsma <jonathon jongsma collabora co uk>
+ *          Danielle Madeley <danielle madeley 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 __FACEBOOK_ITEM_VIEW_H__
+#define __FACEBOOK_ITEM_VIEW_H__
+
+#include <glib-object.h>
+#include <libsocialweb/sw-item-view.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_FACEBOOK_ITEM_VIEW	(sw_facebook_item_view_get_type ())
+#define SW_FACEBOOK_ITEM_VIEW(obj)	(G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_FACEBOOK_ITEM_VIEW, SwFacebookItemView))
+#define SW_FACEBOOK_ITEM_VIEW_CLASS(obj)	(G_TYPE_CHECK_CLASS_CAST ((obj), SW_TYPE_FACEBOOK_ITEM_VIEW, SwFacebookItemViewClass))
+#define SW_IS_FACEBOOK_ITEM_VIEW(obj)	(G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_FACEBOOK_ITEM_VIEW))
+#define SW_IS_FACEBOOK_ITEM_VIEW_CLASS(obj)	(G_TYPE_CHECK_CLASS_TYPE ((obj), SW_TYPE_FACEBOOK_ITEM_VIEW))
+#define SW_FACEBOOK_ITEM_VIEW_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_FACEBOOK_ITEM_VIEW, SwFacebookItemViewClass))
+
+typedef struct _SwFacebookItemView SwFacebookItemView;
+typedef struct _SwFacebookItemViewClass SwFacebookItemViewClass;
+typedef struct _SwFacebookItemViewPrivate SwFacebookItemViewPrivate;
+
+struct _SwFacebookItemView
+{
+  SwItemView parent;
+  SwFacebookItemViewPrivate *priv;
+};
+
+struct _SwFacebookItemViewClass
+{
+  SwItemViewClass parent_class;
+};
+
+GType sw_facebook_item_view_get_type (void);
+
+G_END_DECLS
+
+#endif
diff --git a/services/facebook/facebook-util.c b/services/facebook/facebook-util.c
new file mode 100644
index 0000000..b969602
--- /dev/null
+++ b/services/facebook/facebook-util.c
@@ -0,0 +1,171 @@
+/*
+ * Facebook service utility functions
+ *
+ * Copyright (C) 2010-2011 Collabora Ltd.
+ *
+ * Authors: Jonathon Jongsma <jonathon jongsma 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 <config.h>
+#include <time.h>
+#include <string.h>
+
+#include <libsoup/soup.h>
+
+#include <libsocialweb/sw-service.h>
+
+#include "facebook-util.h"
+
+char *
+build_picture_url (RestProxy *proxy,
+                   char *object,
+                   char *size)
+{
+  char *base_url = NULL, *pic_url = NULL;
+  g_object_get (proxy, "url-format", &base_url, NULL);
+  pic_url = g_strdup_printf ("%s/%s/picture?type=%s",
+                             base_url, object, size);
+  g_free (base_url);
+
+  return pic_url;
+}
+
+JsonNode *
+json_node_from_call (RestProxyCall *call, GError** error)
+{
+  JsonNode *root;
+  JsonObject *object = NULL;
+  char *error_message = NULL;
+  JsonParser *parser = NULL;
+
+  g_return_val_if_fail (call, NULL);
+
+  if (!SOUP_STATUS_IS_SUCCESSFUL (rest_proxy_call_get_status_code (call))) {
+    g_set_error (error, SW_SERVICE_ERROR,
+                 SW_SERVICE_ERROR_REMOTE_ERROR,
+                 "Error from Facebook: %s (%d)",
+                 rest_proxy_call_get_status_message (call),
+                 rest_proxy_call_get_status_code (call));
+    g_object_unref (parser);
+    return NULL;
+  }
+
+  parser = json_parser_new ();
+  if (!json_parser_load_from_data (parser,
+                                   rest_proxy_call_get_payload (call),
+                                   rest_proxy_call_get_payload_length (call),
+                                   NULL)) {
+    g_set_error (error, SW_SERVICE_ERROR,
+                 SW_SERVICE_ERROR_REMOTE_ERROR,
+                 "Malformed JSON from Facebook: %s",
+                 rest_proxy_call_get_payload (call));
+    g_object_unref (parser);
+    return NULL;
+  }
+
+  root = json_parser_get_root (parser);
+
+  if (root)
+    root = json_node_copy (root);
+
+  g_object_unref (parser);
+
+  if (root == NULL) {
+    g_set_error (error, SW_SERVICE_ERROR,
+                 SW_SERVICE_ERROR_REMOTE_ERROR,
+                 "Error from Facebook: %s",
+                 rest_proxy_call_get_payload (call));
+    return NULL;
+  }
+
+  /*
+   * Is it an error?  If so, it'll be a hash containing
+   * the key "error", which maps to a hash containing
+   * a key "message".
+   */
+
+  if (json_node_get_node_type (root) == JSON_NODE_OBJECT) {
+    object = json_node_get_object (root);
+  }
+
+  if (object && json_object_has_member (object, "error")) {
+    JsonNode *inner = json_object_get_member (object,
+                                              "error");
+    JsonObject *inner_object = NULL;
+
+    if (json_node_get_node_type (inner) == JSON_NODE_OBJECT)
+      inner_object = json_node_get_object (inner);
+
+    if (inner_object && json_object_has_member (inner_object, "message"))
+      error_message = get_child_node_value (inner, "mesage");
+  }
+
+  if (error_message) {
+    g_set_error (error, SW_SERVICE_ERROR,
+                 SW_SERVICE_ERROR_REMOTE_ERROR,
+                 "Error response from Facebook: %s", error_message);
+    g_free (error_message);
+    json_node_free (root);
+    return NULL;
+  } else {
+    return root;
+  }
+}
+
+/*
+ * For a given parent @node, get the child node called @name and return a copy
+ * of the content, or NULL. If the content is the empty string, NULL is
+ * returned.
+ */
+char *
+get_child_node_value (JsonNode *node, const char *name)
+{
+  JsonNode *subnode;
+  JsonObject *object;
+  GValue value = {0};
+  const char *string;
+  char *result = NULL;
+
+  if (!node || !name)
+    return NULL;
+
+  if (json_node_get_node_type (node) == JSON_NODE_OBJECT) {
+    object = json_node_get_object (node);
+  } else {
+    return NULL;
+  }
+
+  if (!json_object_has_member (object, name)) {
+    return NULL;
+  }
+
+  subnode = json_object_get_member (object, name);
+
+  if (!subnode)
+    return NULL;
+
+  json_node_get_value (subnode, &value);
+
+  string = g_value_get_string (&value);
+
+  if (string && string[0]) {
+    result = g_strdup (string);
+  }
+
+  g_value_unset (&value);
+
+  return result;
+}
diff --git a/services/facebook/facebook-util.h b/services/facebook/facebook-util.h
new file mode 100644
index 0000000..e66e65c
--- /dev/null
+++ b/services/facebook/facebook-util.h
@@ -0,0 +1,40 @@
+/*
+ * Facebook service utility functions
+ *
+ * Copyright (C) 2010-2011 Collabora Ltd.
+ *
+ * Authors: Jonathon Jongsma <jonathon jongsma 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 _FACEBOOK_UTIL_H
+#define _FACEBOOK_UTIL_H
+
+#include <glib.h>
+#include <rest/rest-proxy.h>
+#include <rest/rest-proxy-call.h>
+#include <json-glib/json-glib.h>
+
+#define FB_PICTURE_SIZE_SQUARE "square"
+#define FB_PICTURE_SIZE_SMALL "small"
+#define FB_PICTURE_SIZE_LARGE "large"
+
+/* Builds a url to a facebook avatar for the given object */
+char* build_picture_url (RestProxy *proxy, char *object, char *size);
+/* utility functions for handling json responses from facebook */
+JsonNode * json_node_from_call (RestProxyCall *call, GError** error);
+char * get_child_node_value (JsonNode *node, const char *name);
+
+#endif /* _FACEBOOK_UTIL_H */
diff --git a/services/facebook/facebook.c b/services/facebook/facebook.c
new file mode 100644
index 0000000..9a6073a
--- /dev/null
+++ b/services/facebook/facebook.c
@@ -0,0 +1,756 @@
+/*
+ * libsocialweb Facebook service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ * Copyright (C) Collabora Ltd.
+ *
+ * Authors: Gary Ching-Pang Lin <glin novell com>
+ *          Thomas Thurman <thomas thurman collabora co uk>
+ *          Jonathon Jongsma <jonathon jongsma collabora co uk>
+ *          Danielle Madeley <danielle madeley 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 <config.h>
+#include <time.h>
+#include <string.h>
+
+#include "facebook.h"
+#include "facebook-util.h"
+#include "facebook-item-view.h"
+
+#include <json-glib/json-glib.h>
+#include <rest/oauth2-proxy.h>
+
+#include <libsocialweb/sw-utils.h>
+#include <libsocialweb/sw-web.h>
+#include <libsocialweb-keystore/sw-keystore.h>
+#include <libsocialweb-keyfob/sw-keyfob.h>
+#include <libsocialweb/sw-online.h>
+#include <libsocialweb/sw-client-monitor.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <interfaces/sw-avatar-ginterface.h>
+#include <interfaces/sw-status-update-ginterface.h>
+#include <interfaces/sw-photo-upload-ginterface.h>
+#include <interfaces/sw-video-upload-ginterface.h>
+#include <interfaces/sw-query-ginterface.h>
+
+static void initable_iface_init (gpointer g_iface, gpointer iface_data);
+static void query_iface_init (gpointer g_iface, gpointer iface_data);
+static void avatar_iface_init (gpointer g_iface, gpointer iface_data);
+static void status_update_iface_init (gpointer g_iface, gpointer iface_data);
+static void photo_upload_iface_init (gpointer g_iface, gpointer iface_data);
+static void video_upload_iface_init (gpointer g_iface, gpointer iface_data);
+
+G_DEFINE_TYPE_WITH_CODE (SwServiceFacebook,
+                         sw_service_facebook,
+                         SW_TYPE_SERVICE,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                initable_iface_init)
+                         G_IMPLEMENT_INTERFACE (SW_TYPE_QUERY_IFACE,
+                                                query_iface_init)
+                         G_IMPLEMENT_INTERFACE (SW_TYPE_AVATAR_IFACE,
+                                                avatar_iface_init)
+                         G_IMPLEMENT_INTERFACE (SW_TYPE_STATUS_UPDATE_IFACE,
+                                                status_update_iface_init)
+                         G_IMPLEMENT_INTERFACE (SW_TYPE_PHOTO_UPLOAD_IFACE,
+                                                photo_upload_iface_init)
+                         G_IMPLEMENT_INTERFACE (SW_TYPE_VIDEO_UPLOAD_IFACE,
+                                                video_upload_iface_init));
+
+#define GET_PRIVATE(o) (((SwServiceFacebook *) o)->priv)
+
+enum {
+  UPLOAD_PHOTO,
+  UPLOAD_VIDEO
+} typedef UploadType;
+
+struct _SwServiceFacebookPrivate {
+  gboolean inited;
+  gboolean online;
+  RestProxy *proxy;
+  RestProxy *video_proxy;
+  char *uid;
+  char *display_name;
+  char *profile_url;
+  char *pic_square;
+};
+
+static GList *service_list;
+static const char **
+get_static_caps (SwService *service)
+{
+  static const char * caps[] = {
+    HAS_UPDATE_STATUS_IFACE,
+    HAS_AVATAR_IFACE,
+    HAS_BANISHABLE_IFACE,
+    HAS_QUERY_IFACE,
+    HAS_PHOTO_UPLOAD_IFACE,
+    HAS_VIDEO_UPLOAD_IFACE,
+
+    /* deprecated */
+    CAN_UPDATE_STATUS,
+    CAN_REQUEST_AVATAR,
+
+    NULL
+  };
+
+  return caps;
+}
+
+static const char **
+get_dynamic_caps (SwService *service)
+{
+  SwServiceFacebookPrivate *priv = GET_PRIVATE (service);
+  static const char *offline_caps[] = {
+    IS_CONFIGURED,
+    NULL
+  };
+  static const char *full_caps[] = {
+    CAN_UPDATE_STATUS,
+    CAN_REQUEST_AVATAR,
+    IS_CONFIGURED,
+    CREDENTIALS_VALID,
+    NULL
+  };
+  static const char *no_caps[] = { NULL };
+
+  if (!priv->uid)
+    return no_caps;
+  else if (priv->online)
+    return full_caps;
+  else
+    return offline_caps;
+}
+
+static void
+clear_user_info (SwServiceFacebook *facebook)
+{
+  SwServiceFacebookPrivate *priv = facebook->priv;
+
+  g_free (priv->uid);
+  priv->uid = NULL;
+  g_free (priv->display_name);
+  priv->display_name = NULL;
+  g_free (priv->profile_url);
+  priv->profile_url = NULL;
+  g_free (priv->pic_square);
+  priv->pic_square = NULL;
+}
+
+static gboolean
+_facebook_extract_user_info (SwServiceFacebook *facebook,
+                             JsonNode *node)
+{
+  SwServiceFacebookPrivate *priv = facebook->priv;
+
+  clear_user_info (facebook);
+  priv->uid = get_child_node_value(node, "id");
+  priv->display_name = get_child_node_value(node, "name");
+  priv->profile_url = get_child_node_value(node, "link");
+
+  /* we don't currently use the profile url for anything important, so if it's
+   * missing, we don't particularly care */
+  if (!priv->uid || !priv->display_name) {
+    clear_user_info (facebook);
+    return FALSE;
+  }
+
+  priv->pic_square = build_picture_url (priv->proxy, priv->uid, FB_PICTURE_SIZE_SQUARE);
+  return TRUE;
+}
+
+static void
+got_user_info_cb (RestProxyCall *call,
+                  const GError  *error,
+                  GObject       *weak_object,
+                  gpointer       userdata)
+{
+  SwService *service = SW_SERVICE (weak_object);
+  SwServiceFacebook *facebook = SW_SERVICE_FACEBOOK (service);
+  JsonNode *node;
+
+  if (error) {
+    g_message ("Error: %s", error->message);
+    return;
+  }
+
+  node = json_node_from_call (call, NULL);
+  if (!node)
+    return;
+
+  _facebook_extract_user_info (facebook, node);
+
+  json_node_free (node);
+
+  sw_service_emit_capabilities_changed
+    (service, get_dynamic_caps (service));
+}
+
+static void
+got_tokens_cb (RestProxy *proxy, gboolean authorised, gpointer user_data)
+{
+  SwServiceFacebook *facebook = SW_SERVICE_FACEBOOK (user_data);
+  SwServiceFacebookPrivate *priv = facebook->priv;
+  RestProxyCall *call;
+
+  if (authorised) {
+    call = rest_proxy_new_call (priv->proxy);
+    rest_proxy_call_set_function (call, "me");
+    rest_proxy_call_async (call,
+                           (RestProxyCallAsyncCallback)got_user_info_cb,
+                           (GObject*)facebook,
+                           NULL,
+                           NULL);
+    g_object_unref (call);
+  }
+}
+
+static const char *
+sw_service_facebook_get_name (SwService *service)
+{
+  return "facebook";
+}
+
+static void
+online_notify (gboolean online, gpointer user_data)
+{
+  SwServiceFacebook *service = (SwServiceFacebook *) user_data;
+  SwServiceFacebookPrivate *priv = service->priv;
+  priv->online = online;
+
+  if (online) {
+    sw_keyfob_oauth2 ((OAuth2Proxy *)priv->proxy, got_tokens_cb, service);
+  } else {
+    sw_service_emit_capabilities_changed ((SwService *)service,
+                                          get_dynamic_caps ((SwService*)service));
+    g_free (priv->uid);
+    priv->uid = NULL;
+  }
+}
+
+static void
+_credentials_updated_func (gpointer data, gpointer userdata)
+{
+  SwService *service = SW_SERVICE (data);
+  SwServiceFacebookPrivate *priv = SW_SERVICE_FACEBOOK (service)->priv;
+
+  online_notify (FALSE, service);
+  /* Clean up pic_square to prevent avatar retrieving */
+  if (priv->pic_square){
+     g_free (priv->pic_square);
+     priv->pic_square = NULL;
+  }
+  online_notify (TRUE, service);
+
+  sw_service_emit_user_changed (service);
+  sw_service_emit_capabilities_changed ((SwService *)service,
+                                        get_dynamic_caps (service));
+}
+
+static void
+credentials_updated (SwService *service)
+{
+  /* If we're online, force a reconnect to fetch new credentials */
+  if (sw_is_online ()) {
+    g_list_foreach (service_list, _credentials_updated_func, NULL);
+  }
+}
+
+static void
+sw_service_facebook_dispose (GObject *object)
+{
+  SwServiceFacebookPrivate *priv = SW_SERVICE_FACEBOOK (object)->priv;
+
+  service_list = g_list_remove (service_list, SW_SERVICE_FACEBOOK (object));
+
+  sw_online_remove_notify (online_notify, object);
+
+  if (priv->proxy) {
+    g_object_unref (priv->proxy);
+    priv->proxy = NULL;
+  }
+
+  if (priv->video_proxy) {
+    g_object_unref (priv->video_proxy);
+    priv->video_proxy = NULL;
+  }
+
+  G_OBJECT_CLASS (sw_service_facebook_parent_class)->dispose (object);
+}
+
+static void
+sw_service_facebook_finalize (GObject *object)
+{
+  SwServiceFacebookPrivate *priv = SW_SERVICE_FACEBOOK (object)->priv;
+
+  g_free (priv->uid);
+  g_free (priv->display_name);
+  g_free (priv->profile_url);
+  g_free (priv->pic_square);
+
+  G_OBJECT_CLASS (sw_service_facebook_parent_class)->finalize (object);
+}
+
+static void
+sw_service_facebook_class_init (SwServiceFacebookClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  SwServiceClass *service_class = SW_SERVICE_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (SwServiceFacebookPrivate));
+
+  object_class->dispose = sw_service_facebook_dispose;
+  object_class->finalize = sw_service_facebook_finalize;
+
+  service_class->get_name = sw_service_facebook_get_name;
+  service_class->get_static_caps = get_static_caps;
+  service_class->get_dynamic_caps = get_dynamic_caps;
+  service_class->credentials_updated = credentials_updated;
+}
+
+static void
+sw_service_facebook_init (SwServiceFacebook *self)
+{
+  self->priv = G_TYPE_INSTANCE_GET_PRIVATE ((self), SW_TYPE_SERVICE_FACEBOOK,
+      SwServiceFacebookPrivate);
+
+  self->priv->inited = FALSE;
+  service_list = g_list_append (service_list, self);
+}
+
+/* Initable interface */
+static gboolean
+sw_service_facebook_initable (GInitable     *initable,
+                              GCancellable  *cancellable,
+                              GError       **error)
+{
+  SwServiceFacebook *facebook = SW_SERVICE_FACEBOOK (initable);
+  SwServiceFacebookPrivate *priv = facebook->priv;
+  GKeyFile *keys;
+  const char *key = NULL, *secret = NULL;
+  char *auth_url = NULL, *graph_url = NULL, *video_url = NULL;
+  char *filename;
+  gboolean rv = FALSE;
+
+  if (priv->inited)
+    return TRUE;
+
+  /* we don't actually care about secret */
+  sw_keystore_get_key_secret ("facebook", &key, &secret);
+  if (key == NULL) {
+    g_set_error_literal (error,
+                         SW_SERVICE_ERROR,
+                         SW_SERVICE_ERROR_NO_KEYS,
+                         "No API key configured");
+    return FALSE;
+  }
+
+  keys = g_key_file_new ();
+
+  filename = g_build_filename("libsocialweb",
+                              "services",
+                              "facebook.keys",
+                              NULL);
+
+  g_key_file_load_from_data_dirs (keys,
+                                  filename,
+                                  NULL, G_KEY_FILE_NONE, NULL);
+
+  g_free (filename);
+
+  auth_url = g_key_file_get_string (keys,
+                                    "OAuth2",
+                                    "AuthEndpoint",
+                                    NULL);
+
+  graph_url = g_key_file_get_string (keys,
+                                     "OAuth2",
+                                     "BaseUri",
+                                     NULL);
+
+  video_url = g_key_file_get_string (keys,
+                                     "OAuth2",
+                                     "BaseVideoUri",
+                                     NULL);
+
+  if (auth_url == NULL) {
+    g_warning ("Auth URL not found in keys file");
+    goto out;
+  }
+
+  if (graph_url == NULL) {
+    g_warning ("Graph URL not found in keys file");
+    goto out;
+  }
+
+  if (video_url == NULL) {
+    g_warning ("Video upload URL not found in keys file");
+    goto out;
+  }
+
+  priv->proxy = oauth2_proxy_new (key,
+                                  auth_url,
+                                  graph_url,
+                                  FALSE);
+
+  priv->video_proxy = rest_proxy_new (video_url, FALSE);
+
+  if (sw_is_online ()) {
+    online_notify (TRUE, facebook);
+  }
+  sw_online_add_notify (online_notify, facebook);
+
+  priv->inited = TRUE;
+
+  rv = TRUE;
+
+ out:
+  g_free (auth_url);
+  g_free (video_url);
+  g_free (graph_url);
+  g_key_file_free (keys);
+
+  return rv;
+}
+
+static void
+initable_iface_init (gpointer g_iface, gpointer iface_data)
+{
+  GInitableIface *klass = (GInitableIface *)g_iface;
+
+  klass->init = sw_service_facebook_initable;
+}
+
+/* Query interface */
+static void
+_facebook_query_open_view (SwQueryIface          *self,
+                           const gchar           *query,
+                           GHashTable            *params,
+                           DBusGMethodInvocation *context)
+{
+  SwServiceFacebookPrivate *priv = GET_PRIVATE (self);
+  SwItemView *item_view;
+  const gchar *object_path;
+
+  g_debug ("query = '%s'", query);
+
+  item_view = g_object_new (SW_TYPE_FACEBOOK_ITEM_VIEW,
+                            "service", self,
+                            "proxy", priv->proxy,
+                            "query", query,
+                            "params", params,
+                            NULL);
+  object_path = sw_item_view_get_object_path (item_view);
+
+  /* Ensure the object gets disposed when the client goes away */
+  sw_client_monitor_add (dbus_g_method_get_sender (context),
+                         (GObject *) item_view);
+
+  sw_query_iface_return_from_open_view (context, object_path);
+}
+
+static void
+query_iface_init (gpointer g_iface, gpointer iface_data)
+{
+  SwQueryIfaceClass *klass = (SwQueryIfaceClass*) g_iface;
+
+  sw_query_iface_implement_open_view (klass, _facebook_query_open_view);
+}
+
+/* Avatar interface */
+static void
+_requested_avatar_downloaded_cb (const gchar *uri,
+                                 gchar       *local_path,
+                                 gpointer     userdata)
+{
+  SwService *service = SW_SERVICE (userdata);
+
+  sw_avatar_iface_emit_avatar_retrieved (service, local_path);
+  g_free (local_path);
+}
+
+static void
+_facebook_avatar_request_avatar (SwAvatarIface     *self,
+                                 DBusGMethodInvocation *context)
+{
+  SwServiceFacebookPrivate *priv = GET_PRIVATE (self);
+
+  if (priv->pic_square) {
+    sw_web_download_image_async (priv->pic_square,
+                                 _requested_avatar_downloaded_cb,
+                                 self);
+  }
+
+  sw_avatar_iface_return_from_request_avatar (context);
+}
+
+static void
+avatar_iface_init (gpointer g_iface,
+                   gpointer iface_data)
+{
+  SwAvatarIfaceClass *klass = (SwAvatarIfaceClass *)g_iface;
+
+  sw_avatar_iface_implement_request_avatar (klass, _facebook_avatar_request_avatar);
+}
+
+/* Status Update interface */
+static void
+_update_status_cb (RestProxyCall *call,
+                   const GError  *error,
+                   GObject       *weak_object,
+                   gpointer       userdata)
+{
+  if (error) {
+    g_critical (G_STRLOC ": Error updating status: %s",
+                error->message);
+    sw_status_update_iface_emit_status_updated (weak_object, FALSE);
+  } else {
+//    SW_DEBUG (FACEBOOK, G_STRLOC ": Status updated.");
+    sw_status_update_iface_emit_status_updated (weak_object, TRUE);
+  }
+
+}
+
+static void
+_facebook_status_update_update_status (SwStatusUpdateIface   *self,
+                                       const gchar           *msg,
+                                       GHashTable            *fields,
+                                       DBusGMethodInvocation *context)
+{
+  SwServiceFacebook *facebook = (SwServiceFacebook *)self;
+  SwServiceFacebookPrivate *priv = facebook->priv;
+  RestProxyCall *call;
+
+  if (!priv->proxy)
+    return;
+
+  call = rest_proxy_new_call (priv->proxy);
+  rest_proxy_call_set_function (call, "me/feed");
+  rest_proxy_call_add_param (call, "message", msg);
+  rest_proxy_call_set_method (call, "POST");
+
+  rest_proxy_call_async (call,
+                         (RestProxyCallAsyncCallback)_update_status_cb,
+                         (GObject *)facebook,
+                         NULL,
+                         NULL);
+  sw_status_update_iface_return_from_update_status (context);
+}
+
+static void
+status_update_iface_init (gpointer g_iface,
+                          gpointer iface_data)
+{
+  SwStatusUpdateIfaceClass *klass = (SwStatusUpdateIfaceClass*)g_iface;
+
+  sw_status_update_iface_implement_update_status (klass,
+                                                      _facebook_status_update_update_status);
+}
+
+static gint
+_upload_file (SwServiceFacebook *self,
+    UploadType upload_type,
+    const gchar *filename,
+    GHashTable *extra_fields,
+    RestProxyCallAsyncCallback upload_cb,
+    GError **error)
+{
+  SwServiceFacebookPrivate *priv = self->priv;
+  RestProxyCall *call;
+  RestParam *param;
+  gchar *basename;
+  gchar *content_type;
+  GMappedFile *map;
+  gint opid;
+  GHashTableIter iter;
+  gpointer key, value;
+
+  g_return_val_if_fail (priv->proxy != NULL, -1);
+
+  /* Open the file */
+  map = g_mapped_file_new (filename, FALSE, error);
+  if (*error != NULL) {
+    g_warning ("Error opening file %s: %s", filename, (*error)->message);
+    return -1;
+  }
+
+  /* Get the file information */
+  basename = g_path_get_basename (filename);
+  content_type = g_content_type_guess (
+      filename,
+      (const guchar*) g_mapped_file_get_contents (map),
+      g_mapped_file_get_length (map),
+      NULL);
+
+  if (upload_type != UPLOAD_VIDEO)
+    {
+      call = rest_proxy_new_call (priv->proxy);
+      rest_proxy_call_set_function (call, "me/photos");
+    }
+  else
+    {
+      call = rest_proxy_new_call (priv->video_proxy);
+      rest_proxy_call_set_function (call,
+          "restserver.php?method=video.upload");
+      rest_proxy_call_add_param (call, "access_token",
+          oauth2_proxy_get_access_token (OAUTH2_PROXY (priv->proxy)));
+      rest_proxy_call_add_param (call, "format", "json");
+    }
+
+  g_hash_table_iter_init (&iter, extra_fields);
+  while (g_hash_table_iter_next (&iter, &key, &value))
+    {
+      const gchar *param_key = key;
+      const gchar *param_value = value;
+
+      if (upload_type != UPLOAD_VIDEO && g_strcmp0 (param_key, "title") == 0)
+        param_key = "message";
+
+      rest_proxy_call_add_param (call, param_key, param_value);
+    }
+
+  param = rest_param_new_with_owner (basename,
+                                     g_mapped_file_get_contents (map),
+                                     g_mapped_file_get_length (map),
+                                     content_type,
+                                     basename,
+                                     map,
+                                     (GDestroyNotify)g_mapped_file_unref);
+
+  rest_proxy_call_add_param_full (call, param);
+
+  rest_proxy_call_set_method (call, "POST");
+
+  opid = sw_next_opid ();
+
+  rest_proxy_call_async (call,
+                         upload_cb,
+                         G_OBJECT (self),
+                         GINT_TO_POINTER (opid),
+                         NULL);
+
+  g_free (basename);
+  g_free (content_type);
+
+  return opid;
+}
+
+static void
+_upload_photo_cb (RestProxyCall *call,
+                  const GError  *error,
+                  GObject       *weak_object,
+                  gpointer       user_data)
+{
+  SwServiceFacebook *facebook = SW_SERVICE_FACEBOOK (weak_object);
+  int opid = GPOINTER_TO_INT (user_data);
+
+  if (error) {
+    sw_photo_upload_iface_emit_photo_upload_progress (facebook, opid, -1,
+        error->message);
+  } else {
+    sw_photo_upload_iface_emit_photo_upload_progress (facebook, opid, 100, "");
+  }
+}
+
+static void
+_facebook_photo_upload_upload_photo (SwPhotoUploadIface    *self,
+                                     const gchar           *filename,
+                                     GHashTable            *fields,
+                                     DBusGMethodInvocation *context)
+{
+  SwServiceFacebook *facebook = SW_SERVICE_FACEBOOK (self);
+  gint opid;
+  GError *error = NULL;
+
+  opid = _upload_file (facebook, UPLOAD_PHOTO, filename, fields,
+      (RestProxyCallAsyncCallback) _upload_photo_cb, &error);
+
+  if (error) {
+    dbus_g_method_return_error (context, error);
+    g_error_free (error);
+    return;
+  }
+
+  sw_photo_upload_iface_return_from_upload_photo (context, opid);
+}
+
+static void
+photo_upload_iface_init (gpointer g_iface,
+                         gpointer iface_data)
+{
+  SwPhotoUploadIfaceClass *klass = (SwPhotoUploadIfaceClass*)g_iface;
+
+  sw_photo_upload_iface_implement_upload_photo (
+          klass, _facebook_photo_upload_upload_photo);
+}
+
+static void
+_upload_video_cb (RestProxyCall *call,
+                  const GError  *error,
+                  GObject       *weak_object,
+                  gpointer       user_data)
+{
+  SwServiceFacebook *facebook = SW_SERVICE_FACEBOOK (weak_object);
+  int opid = GPOINTER_TO_INT (user_data);
+
+  if (error) {
+    sw_video_upload_iface_emit_video_upload_progress (facebook, opid, -1,
+        error->message);
+  } else {
+    sw_video_upload_iface_emit_video_upload_progress (facebook, opid, 100, "");
+  }
+}
+
+static void
+_facebook_video_upload_upload_video (SwVideoUploadIface    *self,
+                                     const gchar           *filename,
+                                     GHashTable            *fields,
+                                     DBusGMethodInvocation *context)
+{
+  SwServiceFacebook *facebook = SW_SERVICE_FACEBOOK (self);
+  gint opid;
+  GError *error = NULL;
+
+  opid = _upload_file (facebook, UPLOAD_VIDEO, filename, fields,
+      (RestProxyCallAsyncCallback) _upload_video_cb, &error);
+
+  if (error) {
+    dbus_g_method_return_error (context, error);
+    g_error_free (error);
+    return;
+  }
+
+  sw_video_upload_iface_return_from_upload_video (context, opid);
+}
+
+static void
+video_upload_iface_init (gpointer g_iface,
+                         gpointer iface_data)
+{
+  SwVideoUploadIfaceClass *klass = (SwVideoUploadIfaceClass*)g_iface;
+
+  sw_video_upload_iface_implement_upload_video (
+          klass, _facebook_video_upload_upload_video);
+}
+
+const char *
+sw_service_facebook_get_uid (SwServiceFacebook *self)
+{
+  g_return_val_if_fail (SW_IS_SERVICE_FACEBOOK (self), NULL);
+
+  return GET_PRIVATE (self)->uid;
+}
diff --git a/services/facebook/facebook.h b/services/facebook/facebook.h
new file mode 100644
index 0000000..6817236
--- /dev/null
+++ b/services/facebook/facebook.h
@@ -0,0 +1,63 @@
+/*
+ * libsocialweb Facebook service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * Author: Gary Ching-Pang Lin <glin novell com>
+ *
+ * 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_SERVICE_FACEBOOK
+#define _SW_SERVICE_FACEBOOK
+
+#include <libsocialweb/sw-service.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_SERVICE_FACEBOOK sw_service_facebook_get_type()
+
+#define SW_SERVICE_FACEBOOK(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_SERVICE_FACEBOOK, SwServiceFacebook))
+
+#define SW_SERVICE_FACEBOOK_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_SERVICE_FACEBOOK, SwServiceFacebookClass))
+
+#define SW_IS_SERVICE_FACEBOOK(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_SERVICE_FACEBOOK))
+
+#define SW_IS_SERVICE_FACEBOOK_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_SERVICE_FACEBOOK))
+
+#define SW_SERVICE_FACEBOOK_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_SERVICE_FACEBOOK, SwServiceFacebookClass))
+
+typedef struct _SwServiceFacebookPrivate SwServiceFacebookPrivate;
+
+typedef struct {
+  SwService parent;
+  SwServiceFacebookPrivate *priv;
+} SwServiceFacebook;
+
+typedef struct {
+  SwServiceClass parent_class;
+} SwServiceFacebookClass;
+
+GType sw_service_facebook_get_type (void);
+
+const char *sw_service_facebook_get_uid (SwServiceFacebook *self);
+
+G_END_DECLS
+
+#endif /* _SW_SERVICE_FACEBOOK */
diff --git a/services/facebook/facebook.keys.in b/services/facebook/facebook.keys.in
new file mode 100644
index 0000000..f11a727
--- /dev/null
+++ b/services/facebook/facebook.keys.in
@@ -0,0 +1,11 @@
+[LibSocialWebService]
+_Name=Facebook
+_Description=Facebook helps you connect and share with the people in your life.
+Link=http://www.facebook.com/
+AuthType=oauth2-facebook
+
+[OAuth2]
+BaseUri=https://graph.facebook.com
+BaseVideoUri=https://api-video.facebook.com
+AuthEndpoint=https://graph.facebook.com/oauth/authorize
+AuthRedirectUri=http://www.facebook.com/connect/login_success.html
diff --git a/services/facebook/module.c b/services/facebook/module.c
new file mode 100644
index 0000000..31de675
--- /dev/null
+++ b/services/facebook/module.c
@@ -0,0 +1,35 @@
+/*
+ * libsocialweb Facebook service support
+ *
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * Author: Gary Ching-Pang Lin <glin novell com>
+ *
+ * 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 <libsocialweb/sw-module.h>
+#include "facebook.h"
+
+const gchar *
+sw_module_get_name (void)
+{
+  return "facebook";
+}
+
+const GType
+sw_module_get_type (void)
+{
+  return SW_TYPE_SERVICE_FACEBOOK;
+}



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