[libsocialweb: 1/9] facebook: Added Facebook service.
- From: Eitan Isaacson <eitani src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libsocialweb: 1/9] facebook: Added Facebook service.
- Date: Fri, 18 Feb 2011 18:52:32 +0000 (UTC)
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]