[libsocialweb] Add MySpace service



commit 3b03ddb29724e0f29ab99710db2f8d8540f6dd49
Author: Gary Ching-Pang Lin <chingpang gmail com>
Date:   Fri Apr 15 12:39:19 2011 +0800

    Add MySpace service

 configure.ac                         |    2 +
 services/Makefile.am                 |    4 +
 services/myspace/Makefile.am         |   27 ++
 services/myspace/module.c            |   33 ++
 services/myspace/myspace-item-view.c |  612 ++++++++++++++++++++++++++++++++++
 services/myspace/myspace-item-view.h |   57 ++++
 services/myspace/myspace.c           |  553 ++++++++++++++++++++++++++++++
 services/myspace/myspace.h           |   59 ++++
 services/myspace/myspace.keys.in     |   12 +
 services/myspace/myspace.png         |  Bin 0 -> 6189 bytes
 10 files changed, 1359 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 7244083..7fd4bde 100644
--- a/configure.ac
+++ b/configure.ac
@@ -169,6 +169,7 @@ SOCIALWEB_ENABLE_SERVICE(Twitter, twitter, TWITTER)
 SOCIALWEB_ENABLE_SERVICE(Vimeo, vimeo, VIMEO)
 SOCIALWEB_ENABLE_SERVICE(Youtube, youtube, YOUTUBE)
 SOCIALWEB_ENABLE_SERVICE(Sina, sina, SINA)
+SOCIALWEB_ENABLE_SERVICE(Myspace, myspace, MYSPACE)
 
 servicesdir='${libdir}'/libsocialweb/services
 AC_SUBST(servicesdir)
@@ -262,6 +263,7 @@ AC_OUTPUT([
         services/vimeo/Makefile
         services/youtube/Makefile
         services/sina/Makefile
+        services/myspace/Makefile
         tests/Makefile
         tools/Makefile
         examples/Makefile
diff --git a/services/Makefile.am b/services/Makefile.am
index e8377ae..60bd25e 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -12,6 +12,10 @@ if WITH_LASTFM
 SUBDIRS += lastfm
 endif
 
+if WITH_MYSPACE
+SUBDIRS += myspace
+endif
+
 if WITH_PHOTOBUCKET
 SUBDIRS += photobucket
 endif
diff --git a/services/myspace/Makefile.am b/services/myspace/Makefile.am
new file mode 100644
index 0000000..7ee4d2c
--- /dev/null
+++ b/services/myspace/Makefile.am
@@ -0,0 +1,27 @@
+services_LTLIBRARIES = libmyspace.la
+libmyspace_la_SOURCES = module.c \
+			myspace.c \
+			myspace.h \
+			myspace-item-view.h \
+			myspace-item-view.c
+libmyspace_la_CFLAGS = -I$(top_srcdir) \
+		       $(REST_CFLAGS) \
+		       $(KEYRING_CFLAGS) \
+		       $(DBUS_GLIB_CFLAGS) \
+		       $(JSON_GLIB_CFLAGS) \
+		       -DG_LOG_DOMAIN=\"MySpace\"
+libmyspace_la_LIBADD = $(top_builddir)/libsocialweb/libsocialweb.la \
+		       $(top_builddir)/libsocialweb-keystore/libsocialweb-keystore.la \
+		       $(top_builddir)/libsocialweb-keyfob/libsocialweb-keyfob.la \
+		       $(REST_LIBS) \
+		       $(KEYRING_CFLAGS) \
+		       $(DBUS_GLIB_LIBS) \
+		       $(JSON_GLIB_LIBS)
+libmyspace_la_LDFLAGS = -module -avoid-version
+
+dist_servicesdata_DATA = myspace.png
+
+servicesdata_DATA = myspace.keys
+CLEANFILES = myspace.keys
+EXTRA_DIST = myspace.keys.in
+ INTLTOOL_SOCIALWEB_KEYS@
diff --git a/services/myspace/module.c b/services/myspace/module.c
new file mode 100644
index 0000000..06d6bb1
--- /dev/null
+++ b/services/myspace/module.c
@@ -0,0 +1,33 @@
+/*
+ * libsocialweb MySpace service support
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * 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 "myspace.h"
+
+const gchar *
+sw_module_get_name (void)
+{
+  return "myspace";
+}
+
+const GType
+sw_module_get_type (void)
+{
+  return SW_TYPE_SERVICE_MYSPACE;
+}
diff --git a/services/myspace/myspace-item-view.c b/services/myspace/myspace-item-view.c
new file mode 100644
index 0000000..25797a5
--- /dev/null
+++ b/services/myspace/myspace-item-view.c
@@ -0,0 +1,612 @@
+/*
+ * libsocialweb MySpace service support
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+
+#include <rest/rest-proxy.h>
+#include <json-glib/json-glib.h>
+#include <libsoup/soup.h>
+
+#include <libsocialweb/sw-utils.h>
+#include <libsocialweb/sw-debug.h>
+#include <libsocialweb/sw-item.h>
+#include <libsocialweb/sw-cache.h>
+
+#include "myspace-item-view.h"
+#include "myspace.h"
+
+G_DEFINE_TYPE (SwMySpaceItemView,
+               sw_myspace_item_view,
+               SW_TYPE_ITEM_VIEW)
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_MYSPACE_ITEM_VIEW, SwMySpaceItemViewPrivate))
+
+typedef struct _SwMySpaceItemViewPrivate SwMySpaceItemViewPrivate;
+
+struct _SwMySpaceItemViewPrivate {
+  RestProxy *proxy;
+  guint timeout_id;
+  GHashTable *params;
+  gchar *query;
+};
+
+enum
+{
+  PROP_0,
+  PROP_PROXY,
+  PROP_PARAMS,
+  PROP_QUERY
+};
+
+#define UPDATE_TIMEOUT 5 * 60
+
+static void _service_item_hidden_cb (SwService   *service,
+                                     const gchar *uid,
+                                     SwItemView  *item_view);
+static void _service_user_changed_cb (SwService  *service,
+                                      SwItemView *item_view);
+static void _service_capabilities_changed_cb (SwService    *service,
+                                              const gchar **caps,
+                                              SwItemView   *item_view);
+
+static void
+sw_myspace_item_view_get_property (GObject    *object,
+                                guint       property_id,
+                                GValue     *value,
+                                GParamSpec *pspec)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (object);
+
+  switch (property_id) {
+    case PROP_PROXY:
+      g_value_set_object (value, priv->proxy);
+      break;
+    case PROP_PARAMS:
+      g_value_set_boxed (value, priv->params);
+      break;
+    case PROP_QUERY:
+      g_value_set_string (value, priv->query);
+      break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+sw_myspace_item_view_set_property (GObject      *object,
+                                   guint         property_id,
+                                   const GValue *value,
+                                   GParamSpec   *pspec)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (object);
+
+  switch (property_id) {
+    case PROP_PROXY:
+      if (priv->proxy)
+      {
+        g_object_unref (priv->proxy);
+      }
+      priv->proxy = g_value_dup_object (value);
+      break;
+    case PROP_PARAMS:
+      priv->params = g_value_dup_boxed (value);
+      break;
+    case PROP_QUERY:
+      priv->query = g_value_dup_string (value);
+      break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+sw_myspace_item_view_dispose (GObject *object)
+{
+  SwItemView *item_view = SW_ITEM_VIEW (object);
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (object);
+
+  if (priv->proxy) {
+    g_object_unref (priv->proxy);
+    priv->proxy = NULL;
+  }
+
+  if (priv->timeout_id) {
+    g_source_remove (priv->timeout_id);
+    priv->timeout_id = 0;
+  }
+
+  g_signal_handlers_disconnect_by_func (sw_item_view_get_service (item_view),
+                                        _service_item_hidden_cb,
+                                        item_view);
+  g_signal_handlers_disconnect_by_func (sw_item_view_get_service (item_view),
+                                        _service_user_changed_cb,
+                                        item_view);
+  g_signal_handlers_disconnect_by_func (sw_item_view_get_service (item_view),
+                                        _service_capabilities_changed_cb,
+                                        item_view);
+
+  G_OBJECT_CLASS (sw_myspace_item_view_parent_class)->dispose (object);
+}
+
+static void
+sw_myspace_item_view_finalize (GObject *object)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (object);
+
+  /* free private variables */
+  g_free (priv->query);
+  g_hash_table_unref (priv->params);
+
+  G_OBJECT_CLASS (sw_myspace_item_view_parent_class)->finalize (object);
+}
+
+static JsonNode *
+json_node_from_call (RestProxyCall *call,
+                     const char    *name)
+{
+  JsonParser *parser;
+  JsonNode *root = NULL;
+  GError *error;
+  gboolean ret = FALSE;
+
+  parser = json_parser_new ();
+
+  if (call == NULL)
+    goto out;
+
+  if (!SOUP_STATUS_IS_SUCCESSFUL (rest_proxy_call_get_status_code (call))) {
+    g_message ("Error from %s: %s (%d)",
+               name,
+               rest_proxy_call_get_status_message (call),
+               rest_proxy_call_get_status_code (call));
+    goto out;
+  }
+
+  ret = json_parser_load_from_data (parser,
+                                    rest_proxy_call_get_payload (call),
+                                    rest_proxy_call_get_payload_length (call),
+                                    &error);
+  root = json_parser_get_root (parser);
+
+  if (root == NULL) {
+    g_message ("Error from %s: %s",
+               name,
+               rest_proxy_call_get_payload (call));
+    goto out;
+  }
+
+  root = json_node_copy (root);
+
+out:
+  g_object_unref (parser);
+
+  return root;
+}
+
+static char *
+make_date (const char *s)
+{
+  struct tm tm;
+
+  /* Time format example: 2010-12-07T10:02:22Z */
+  strptime (s, "%FT%T%z", &tm);
+  return sw_time_t_to_string (timegm (&tm));
+}
+
+static SwItem *
+make_item (SwService *service, JsonNode *entry)
+{
+  SwItem *item;
+  JsonNode *author;
+  JsonObject *entry_obj, *author_obj;
+  const char *tmp;
+  char *status;
+
+  item = sw_item_new ();
+  sw_item_set_service (item, service);
+
+  entry_obj = json_node_get_object (entry);
+  author = json_object_get_member (entry_obj, "author");
+  author_obj = json_node_get_object (author);
+
+  /*
+    id: myspace-<statusId>
+    authorid: <userId>
+    author: <displayName>
+    authoricon: <thumbnailUrl>
+    content: <status>
+    date: <moodStatusLastUpdated>
+    url: <profileUrl>
+  */
+
+  /* Construct the id of sw_item */
+  tmp = json_object_get_string_member (entry_obj, "statusId");
+  sw_item_take (item, "id", g_strconcat ("myspace-", tmp, NULL));
+
+  /* Get the user id for authorid */
+  tmp = json_object_get_string_member (entry_obj, "userId");
+  sw_item_put (item, "authorid", tmp);
+
+  /* Get the user name */
+  tmp = json_object_get_string_member (author_obj, "displayName");
+  sw_item_put (item, "author", tmp);
+
+  /* Get the url of avatar */
+  tmp = json_object_get_string_member (author_obj, "thumbnailUrl");
+  sw_item_request_image_fetch (item, FALSE, "authoricon", g_strdup (tmp));;
+
+  /* Get the content */
+  status = json_object_get_string_member (entry_obj, "status");
+  sw_item_put (item, "content", sw_unescape_entities ((gchar *)status));
+  /* TODO: if mood is not "(none)" then append that to the status message */
+
+  /* Get the date */
+  tmp = json_object_get_string_member (entry_obj, "moodStatusLastUpdated");
+  sw_item_take (item, "date", make_date (tmp));
+
+  /* Get the url of this status */
+  /* TODO find out the true url instead of the profile url */
+  tmp = json_object_get_string_member (author_obj, "profileUrl");
+  sw_item_put (item, "url", tmp);
+
+  return item;
+}
+
+static void
+_populate_set_from_node (SwService *service,
+                         SwSet     *set,
+                         JsonNode  *root)
+{
+  JsonNode *entries;
+  JsonArray *status_array;
+  JsonObject *object;
+  guint i, length;
+
+  /*
+  The data format:
+  "entry":[
+    {
+      "author":{
+        "displayName":"username",
+        "id":"myspace.com.person.<id>",
+        "msUserType":"RegularUser",
+        "name":{
+          "familyName":"family name",
+          "givenName":"given name"
+        },
+        "profileUrl":"http://www.myspace.com/<id>",
+        "thumbnailUrl":"url of avatar"
+      },
+      "moodName":"none",
+      "moodStatusLastUpdated":"2010-12-07T10:02:22Z",
+      "numComments":"0",
+      "status":"whatever you said",
+      "statusId":"<status id>",
+      "userId":"myspace.com.person.<id>"
+    },
+    ...
+  ],
+  */
+  object = json_node_get_object (root);
+  entries = json_object_get_member (object, "entry");
+
+  status_array = json_node_get_array (entries);
+  length = json_array_get_length (status_array);
+
+  for (i=0; i<length; i++) {
+    JsonNode *entry = json_array_get_element (status_array, i);
+    SwItem *item;
+
+    item = make_item (service, entry);
+
+    if (!sw_service_is_uid_banned (service, sw_item_get (item, "id"))) {
+      sw_set_add (set, (GObject *)item);
+    }
+
+    g_object_unref (item);
+  }
+}
+
+static void
+_got_status_cb (RestProxyCall *call,
+                const GError  *error,
+                GObject       *weak_object,
+                gpointer       userdata)
+{
+  SwMySpaceItemView *item_view = SW_MYSPACE_ITEM_VIEW (weak_object);
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (item_view);
+  SwSet *set = (SwSet *)userdata;
+  JsonNode *root;
+  SwService *service;
+
+  if (error) {
+    g_message ("Error: %s", error->message);
+    return;
+  }
+
+  service = sw_item_view_get_service (SW_ITEM_VIEW (item_view));
+
+  root = json_node_from_call (call, "MySpace");
+  if (root == NULL)
+    return;
+
+  _populate_set_from_node (service, set, root);
+
+  g_object_unref (call);
+
+  sw_item_view_set_from_set (SW_ITEM_VIEW (item_view), set);
+
+  /* Save the results of this set to the cache */
+  sw_cache_save (service,
+                 priv->query,
+                 priv->params,
+                 set);
+
+  sw_set_unref (set);
+
+  g_object_unref (root);
+}
+
+static void
+_get_user_status_updates (SwMySpaceItemView *item_view,
+                          SwSet             *set)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (item_view);
+  RestProxyCall *call;
+
+  call = rest_proxy_new_call (priv->proxy);
+
+  rest_proxy_call_set_function (call, "1.0/statusmood/@me/@self/history");
+  rest_proxy_call_add_params(call,
+                             "count", "20",
+                             "fields", "author",
+                             NULL);
+
+  rest_proxy_call_async (call, _got_status_cb, (GObject*)item_view, set, NULL);
+}
+
+static void
+_get_friends_status_updates (SwMySpaceItemView *item_view,
+                             SwSet             *set)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (item_view);
+  RestProxyCall *call;
+
+  call = rest_proxy_new_call (priv->proxy);
+
+  rest_proxy_call_set_function (call, "1.0/statusmood/@me/@friends/history");
+  rest_proxy_call_add_params(call,
+                             "includeself", "true",
+                             "count", "20",
+                             "fields", "author",
+                             NULL);
+
+  rest_proxy_call_async (call, _got_status_cb, (GObject*)item_view, set, NULL);
+}
+
+static void
+_get_status_updates (SwMySpaceItemView *item_view)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (item_view);
+  GHashTable *params = NULL;
+  SwSet *set;
+
+  set = sw_item_set_new ();
+
+  if (g_str_equal (priv->query, "own"))
+    _get_user_status_updates (item_view, set);
+  else if (g_str_equal (priv->query, "feed"))
+    _get_friends_status_updates (item_view, set);
+  else
+    g_error (G_STRLOC ": Unexpected query '%s'", priv->query);
+
+  if (params)
+    g_hash_table_unref (params);
+}
+
+static gboolean
+_update_timeout_cb (gpointer data)
+{
+  SwMySpaceItemView *item_view = SW_MYSPACE_ITEM_VIEW (data);
+
+  _get_status_updates (item_view);
+
+  return TRUE;
+}
+
+static void
+_load_from_cache (SwMySpaceItemView *item_view)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (item_view);
+  SwSet *set;
+
+  set = sw_cache_load (sw_item_view_get_service (SW_ITEM_VIEW (item_view)),
+                       priv->query,
+                       priv->params,
+                       sw_item_set_new);
+
+  if (set)
+  {
+    sw_item_view_set_from_set (SW_ITEM_VIEW (item_view),
+                               set);
+    sw_set_unref (set);
+  }
+}
+
+static void
+myspace_item_view_start (SwItemView *item_view)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (item_view);
+
+  if (priv->timeout_id)
+  {
+    g_warning (G_STRLOC ": View already started.");
+  } else {
+    priv->timeout_id = g_timeout_add_seconds (UPDATE_TIMEOUT,
+                                              (GSourceFunc)_update_timeout_cb,
+                                              item_view);
+    _load_from_cache ((SwMySpaceItemView *)item_view);
+    _get_status_updates ((SwMySpaceItemView *)item_view);
+  }
+}
+
+static void
+myspace_item_view_stop (SwItemView *item_view)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE (item_view);
+
+  if (!priv->timeout_id)
+  {
+    g_warning (G_STRLOC ": View not running");
+  } else {
+    g_source_remove (priv->timeout_id);
+    priv->timeout_id = 0;
+  }
+}
+
+static void
+myspace_item_view_refresh (SwItemView *item_view)
+{
+  _get_status_updates ((SwMySpaceItemView *)item_view);
+}
+
+static void
+_service_item_hidden_cb (SwService   *service,
+                         const gchar *uid,
+                         SwItemView  *item_view)
+{
+  sw_item_view_remove_by_uid (item_view, uid);
+}
+
+static void
+_service_user_changed_cb (SwService  *service,
+                          SwItemView *item_view)
+{
+  SwSet *set;
+
+  /* We need to empty the set */
+  set = sw_item_set_new ();
+  sw_item_view_set_from_set (SW_ITEM_VIEW (item_view),
+                             set);
+  sw_set_unref (set);
+
+  /* And drop the cache */
+  sw_cache_drop_all (service);
+}
+
+static void
+_service_capabilities_changed_cb (SwService    *service,
+                                  const gchar **caps,
+                                  SwItemView   *item_view)
+{
+  SwMySpaceItemViewPrivate *priv = GET_PRIVATE ((SwMySpaceItemView*) item_view);
+
+  if (sw_service_has_cap (caps, CREDENTIALS_VALID))
+  {
+    myspace_item_view_refresh (item_view);
+
+    if (!priv->timeout_id)
+    {
+      priv->timeout_id = g_timeout_add_seconds (UPDATE_TIMEOUT,
+                                                (GSourceFunc)_update_timeout_cb,
+                                                item_view);
+    }
+  } else {
+    if (priv->timeout_id)
+    {
+      g_source_remove (priv->timeout_id);
+      priv->timeout_id = 0;
+    }
+  }
+}
+
+static void
+sw_myspace_item_view_constructed (GObject *object)
+{
+  SwItemView *item_view = SW_ITEM_VIEW (object);
+
+  g_signal_connect (sw_item_view_get_service (item_view),
+                    "item-hidden",
+                    (GCallback)_service_item_hidden_cb,
+                    item_view);
+
+  g_signal_connect (sw_item_view_get_service (item_view),
+                    "user-changed",
+                    (GCallback)_service_user_changed_cb,
+                    item_view);
+
+  g_signal_connect (sw_item_view_get_service (item_view),
+                    "capabilities-changed",
+                    (GCallback)_service_capabilities_changed_cb,
+                    item_view);
+
+  if (G_OBJECT_CLASS (sw_myspace_item_view_parent_class)->constructed)
+    G_OBJECT_CLASS (sw_myspace_item_view_parent_class)->constructed (object);
+}
+
+static void
+sw_myspace_item_view_class_init (SwMySpaceItemViewClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  SwItemViewClass *item_view_class = SW_ITEM_VIEW_CLASS (klass);
+  GParamSpec *pspec;
+
+  g_type_class_add_private (klass, sizeof (SwMySpaceItemViewPrivate));
+
+  object_class->get_property = sw_myspace_item_view_get_property;
+  object_class->set_property = sw_myspace_item_view_set_property;
+  object_class->dispose = sw_myspace_item_view_dispose;
+  object_class->finalize = sw_myspace_item_view_finalize;
+  object_class->constructed = sw_myspace_item_view_constructed;
+
+  item_view_class->start = myspace_item_view_start;
+  item_view_class->stop = myspace_item_view_stop;
+  item_view_class->refresh = myspace_item_view_refresh;
+
+  pspec = g_param_spec_object ("proxy",
+                               "proxy",
+                               "proxy",
+                               REST_TYPE_PROXY,
+                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+  g_object_class_install_property (object_class, PROP_PROXY, pspec);
+
+  pspec = g_param_spec_string ("query",
+                               "query",
+                               "query",
+                               NULL,
+                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+  g_object_class_install_property (object_class, PROP_QUERY, pspec);
+
+  pspec = g_param_spec_boxed ("params",
+                              "params",
+                              "params",
+                              G_TYPE_HASH_TABLE,
+                              G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+  g_object_class_install_property (object_class, PROP_PARAMS, pspec);
+}
+
+static void
+sw_myspace_item_view_init (SwMySpaceItemView *self)
+{
+}
diff --git a/services/myspace/myspace-item-view.h b/services/myspace/myspace-item-view.h
new file mode 100644
index 0000000..413abaf
--- /dev/null
+++ b/services/myspace/myspace-item-view.h
@@ -0,0 +1,57 @@
+/*
+ * libsocialweb MySpace service support
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * 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_MYSPACE_ITEM_VIEW
+#define _SW_MYSPACE_ITEM_VIEW
+
+#include <glib-object.h>
+#include <libsocialweb/sw-item-view.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_MYSPACE_ITEM_VIEW sw_myspace_item_view_get_type()
+
+#define SW_MYSPACE_ITEM_VIEW(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_MYSPACE_ITEM_VIEW, SwMySpaceItemView))
+
+#define SW_MYSPACE_ITEM_VIEW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_MYSPACE_ITEM_VIEW, SwMySpaceItemViewClass))
+
+#define SW_IS_MYSPACE_ITEM_VIEW(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_MYSPACE_ITEM_VIEW))
+
+#define SW_IS_MYSPACE_ITEM_VIEW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_MYSPACE_ITEM_VIEW))
+
+#define SW_MYSPACE_ITEM_VIEW_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_MYSPACE_ITEM_VIEW, SwMySpaceItemViewClass))
+
+typedef struct {
+  SwItemView parent;
+} SwMySpaceItemView;
+
+typedef struct {
+  SwItemViewClass parent_class;
+} SwMySpaceItemViewClass;
+
+GType sw_myspace_item_view_get_type (void);
+
+G_END_DECLS
+
+#endif /* _SW_MYSPACE_ITEM_VIEW */
diff --git a/services/myspace/myspace.c b/services/myspace/myspace.c
new file mode 100644
index 0000000..c4446ee
--- /dev/null
+++ b/services/myspace/myspace.c
@@ -0,0 +1,553 @@
+/*
+ * libsocialweb MySpace service support
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * 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 <gio/gio.h>
+#include <glib/gi18n.h>
+#include <gnome-keyring.h>
+#include <dbus/dbus-glib-lowlevel.h>
+
+#include <libsocialweb/sw-item.h>
+#include <libsocialweb/sw-set.h>
+#include <libsocialweb/sw-online.h>
+#include <libsocialweb/sw-debug.h>
+#include <libsocialweb/sw-utils.h>
+#include <libsocialweb/sw-web.h>
+#include <libsocialweb/sw-client-monitor.h>
+#include <libsocialweb-keyfob/sw-keyfob.h>
+#include <libsocialweb-keystore/sw-keystore.h>
+
+#include <rest/oauth-proxy.h>
+#include <json-glib/json-glib.h>
+
+#include <interfaces/sw-query-ginterface.h>
+#include <interfaces/sw-avatar-ginterface.h>
+#include <interfaces/sw-status-update-ginterface.h>
+
+#include "myspace.h"
+#include "myspace-item-view.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);
+
+G_DEFINE_TYPE_WITH_CODE (SwServiceMySpace,
+                         sw_service_myspace,
+                         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));
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_SERVICE_MYSPACE, SwServiceMySpacePrivate))
+
+struct _SwServiceMySpacePrivate {
+  gboolean inited;
+  RestProxy *proxy;
+  char *user_id;
+  char *image_url;
+};
+
+static JsonNode *
+node_from_call (RestProxyCall *call, JsonParser *parser)
+{
+  JsonNode *root;
+  GError *error;
+  gboolean ret = FALSE;
+
+  if (call == NULL)
+    return NULL;
+
+  if (!SOUP_STATUS_IS_SUCCESSFUL (rest_proxy_call_get_status_code (call))) {
+    g_message ("Error from MySpace: %s (%d)",
+               rest_proxy_call_get_status_message (call),
+               rest_proxy_call_get_status_code (call));
+    return NULL;
+  }
+
+  ret = json_parser_load_from_data (parser,
+                                    rest_proxy_call_get_payload (call),
+                                    rest_proxy_call_get_payload_length (call),
+                                    &error);
+  root = json_parser_get_root (parser);
+
+  if (root == NULL) {
+    g_message ("Error from MySpace: %s",
+               rest_proxy_call_get_payload (call));
+    return NULL;
+  }
+
+  return root;
+}
+
+static gboolean
+account_is_configured ()
+{
+  RestProxy *proxy;
+  gboolean configured = FALSE;
+  const char *key = NULL, *secret = NULL;
+
+  sw_keystore_get_key_secret ("myspace", &key, &secret);
+  proxy = oauth_proxy_new (key, secret, "http://api.myspace.com/";, FALSE);
+
+  configured = sw_keyfob_oauth_sync ((OAuthProxy *)proxy);
+
+  g_object_unref (proxy);
+
+  return configured;
+}
+
+static const char **
+get_static_caps (SwService *service)
+{
+  static const char * caps[] = {
+    CAN_VERIFY_CREDENTIALS,
+    HAS_UPDATE_STATUS_IFACE,
+    HAS_AVATAR_IFACE,
+    HAS_BANISHABLE_IFACE,
+    HAS_QUERY_IFACE,
+
+    /* deprecated */
+    CAN_UPDATE_STATUS,
+    CAN_REQUEST_AVATAR,
+    NULL
+  };
+
+  return caps;
+}
+
+static const char **
+get_dynamic_caps (SwService *service)
+{
+  SwServiceMySpace *myspace = SW_SERVICE_MYSPACE (service);
+  gboolean configured = FALSE;
+  static const char * caps[] = {
+    IS_CONFIGURED,
+    CREDENTIALS_VALID,
+    CAN_UPDATE_STATUS,
+    CAN_REQUEST_AVATAR,
+    NULL
+  };
+  static const char * no_caps[] = { NULL };
+  static const char * configured_caps[] = {
+    IS_CONFIGURED,
+    NULL
+  };
+
+  if (myspace->priv->user_id)
+    return caps;
+
+  configured = account_is_configured ();
+
+  if (configured)
+    return configured_caps;
+  else
+    return no_caps;
+}
+
+static void
+construct_user_data (SwServiceMySpace *myspace, JsonNode *root)
+{
+  SwServiceMySpacePrivate *priv = GET_PRIVATE (myspace);
+  JsonNode *node;
+  JsonObject *object;
+
+  g_free (priv->user_id);
+  g_free (priv->image_url);
+  priv->user_id = NULL;
+  priv->image_url = NULL;
+
+  object = json_node_get_object (root);
+  node = json_object_get_member (object, "person");
+
+  if (!node)
+    return;
+
+  object = json_node_get_object (node);
+
+  priv->user_id = g_strdup (json_object_get_string_member (object, "id"));
+  priv->image_url = g_strdup (json_object_get_string_member (object, "thumbnailUrl"));;
+}
+
+static void
+got_user_cb (RestProxyCall *call,
+             const GError  *error,
+             GObject       *weak_object,
+             gpointer       userdata)
+{
+  SwService *service = SW_SERVICE (weak_object);
+  SwServiceMySpace *myspace = SW_SERVICE_MYSPACE (service);
+  JsonParser *parser = NULL;
+  JsonNode *node;
+
+  if (error) {
+    g_message ("Error: %s", error->message);
+    return;
+  }
+
+  parser = json_parser_new ();
+  node = node_from_call (call, parser);
+  if (node == NULL)
+    return;
+
+  construct_user_data (myspace, node);
+
+  g_object_unref (node);
+  g_object_unref (parser);
+
+  sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+}
+
+static void
+got_tokens_cb (RestProxy *proxy, gboolean authorised, gpointer user_data)
+{
+  SwServiceMySpace *myspace = SW_SERVICE_MYSPACE (user_data);
+  SwServiceMySpacePrivate *priv = GET_PRIVATE (myspace);
+  RestProxyCall *call;
+
+  if (authorised) {
+    call = rest_proxy_new_call (priv->proxy);
+    rest_proxy_call_set_function (call, "1.0/people/@me/@self");
+    rest_proxy_call_async (call, got_user_cb, (GObject*)myspace, NULL, NULL);
+  }
+}
+
+static const char *
+sw_service_myspace_get_name (SwService *service)
+{
+  return "myspace";
+}
+
+static void
+online_notify (gboolean online, gpointer user_data)
+{
+  SwServiceMySpace *service = (SwServiceMySpace *) user_data;
+  SwServiceMySpacePrivate *priv = service->priv;
+
+  if (online) {
+    sw_keyfob_oauth ((OAuthProxy *)priv->proxy, got_tokens_cb, service);
+  } else {
+    g_free (priv->user_id);
+    priv->user_id = NULL;
+
+    g_free (priv->image_url);
+    priv->image_url = NULL;
+
+    sw_service_emit_capabilities_changed ((SwService *)service,
+                                          get_dynamic_caps ((SwService *)service));
+  }
+}
+
+static void
+refresh_credentials (SwServiceMySpace *myspace)
+{
+  online_notify (FALSE, (SwService *)myspace);
+  online_notify (TRUE, (SwService *)myspace);
+}
+
+static void
+credentials_updated (SwService *service)
+{
+  refresh_credentials (SW_SERVICE_MYSPACE (service));
+
+  sw_service_emit_user_changed (service);
+  sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+}
+
+static void
+sw_service_myspace_dispose (GObject *object)
+{
+  SwServiceMySpacePrivate *priv = SW_SERVICE_MYSPACE (object)->priv;
+
+  sw_online_remove_notify (online_notify, object);
+
+  if (priv->proxy) {
+    g_object_unref (priv->proxy);
+    priv->proxy = NULL;
+  }
+
+  g_free (priv->user_id);
+  g_free (priv->image_url);
+
+  G_OBJECT_CLASS (sw_service_myspace_parent_class)->dispose (object);
+}
+
+static void
+sw_service_myspace_finalize (GObject *object)
+{
+  SwServiceMySpacePrivate *priv = SW_SERVICE_MYSPACE (object)->priv;
+
+  g_free (priv->user_id);
+
+  G_OBJECT_CLASS (sw_service_myspace_parent_class)->finalize (object);
+}
+
+static void
+sw_service_myspace_class_init (SwServiceMySpaceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  SwServiceClass *service_class = SW_SERVICE_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (SwServiceMySpacePrivate));
+
+  object_class->dispose = sw_service_myspace_dispose;
+  object_class->finalize = sw_service_myspace_finalize;
+
+  service_class->get_name = sw_service_myspace_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_myspace_init (SwServiceMySpace *self)
+{
+  self->priv = GET_PRIVATE (self);
+  self->priv->inited = FALSE;
+}
+
+/* Initable interface */
+
+static gboolean
+sw_service_myspace_initable (GInitable     *initable,
+                             GCancellable  *cancellable,
+                             GError       **error)
+{
+  SwServiceMySpace *myspace = SW_SERVICE_MYSPACE (initable);
+  SwServiceMySpacePrivate *priv = myspace->priv;
+  const char *key = NULL, *secret = NULL;
+
+  if (priv->inited)
+    return TRUE;
+
+  sw_keystore_get_key_secret ("myspace", &key, &secret);
+  if (key == NULL || secret == NULL) {
+    g_set_error_literal (error,
+                         SW_SERVICE_ERROR,
+                         SW_SERVICE_ERROR_NO_KEYS,
+                         "No API key configured");
+    return FALSE;
+  }
+  priv->proxy = oauth_proxy_new (key, secret, "http://api.myspace.com/";, FALSE);
+
+  sw_online_add_notify (online_notify, myspace);
+
+  refresh_credentials (myspace);
+
+  priv->inited = TRUE;
+
+  return TRUE;
+}
+
+static void
+initable_iface_init (gpointer g_iface, gpointer iface_data)
+{
+  GInitableIface *klass = (GInitableIface *)g_iface;
+
+  klass->init = sw_service_myspace_initable;
+}
+
+/* Query interface */
+
+static const gchar *valid_queries[] = {"feed",
+                                       "own"};
+static gboolean
+_check_query_validity (const gchar *query)
+{
+  gint i = 0;
+
+  for (i = 0; i < G_N_ELEMENTS(valid_queries); i++)
+  {
+    if (g_str_equal (query, valid_queries[i]))
+      return TRUE;
+  }
+
+  return FALSE;
+}
+
+static void
+_myspace_query_open_view (SwQueryIface          *self,
+                          const gchar           *query,
+                          GHashTable            *params,
+                          DBusGMethodInvocation *context)
+{
+  SwServiceMySpacePrivate *priv = GET_PRIVATE (self);
+  SwItemView *item_view;
+  const gchar *object_path;
+
+  if (!_check_query_validity (query))
+  {
+    dbus_g_method_return_error (context,
+                                g_error_new (SW_SERVICE_ERROR,
+                                             SW_SERVICE_ERROR_INVALID_QUERY,
+                                             "Query '%s' is invalid",
+                                             query));
+    return;
+  }
+
+  item_view = g_object_new (SW_TYPE_MYSPACE_ITEM_VIEW,
+                            "proxy", priv->proxy,
+                            "service", self,
+                            "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,
+                                      _myspace_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
+_myspace_avatar_request_avatar (SwAvatarIface     *self,
+                                DBusGMethodInvocation *context)
+{
+  SwServiceMySpacePrivate *priv = GET_PRIVATE (self);
+
+  if (priv->image_url) {
+    sw_web_download_image_async (priv->image_url,
+                                 _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, _myspace_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_status_update_iface_emit_status_updated (weak_object, TRUE);
+  }
+}
+
+gchar *request_body = NULL;
+
+static gboolean
+_myspace_serialize_params (RestProxyCall *call,
+                           gchar **content_type,
+                           gchar **content,
+                           gsize *content_len,
+                           GError **error)
+{
+  static gsize len;
+  len = strlen (request_body);
+
+  *content_type = g_strdup ("text/plain");
+  *content = g_strdup (request_body);
+  *content_len = len;
+  error = NULL;
+
+  return TRUE;
+}
+
+static void
+_myspace_status_update_update_status (SwStatusUpdateIface   *self,
+                                      const gchar           *msg,
+                                      GHashTable            *fields,
+                                      DBusGMethodInvocation *context)
+{
+  SwServiceMySpace *myspace = (SwServiceMySpace *)self;
+  SwServiceMySpacePrivate *priv = myspace->priv;
+  RestProxyCall *call;
+  RestProxyCallClass *call_class;
+  gchar *escaped_msg;
+
+  if (!priv->user_id)
+    return;
+
+  call = rest_proxy_new_call (priv->proxy);
+  call_class = REST_PROXY_CALL_GET_CLASS (call);
+  rest_proxy_call_set_method (call, "PUT");
+  rest_proxy_call_set_function (call, "1.0/statusmood/@me/@self");
+
+  escaped_msg = g_markup_escape_text (msg, -1);
+  request_body = g_strdup_printf ("{ \"status\":\"%s\" }", escaped_msg);
+  call_class->serialize_params = _myspace_serialize_params;
+
+  rest_proxy_call_async (call, _update_status_cb, (GObject *)self, NULL, NULL);
+  call_class->serialize_params = NULL;
+  sw_status_update_iface_return_from_update_status (context);
+
+  g_free (request_body);
+  g_free (escaped_msg);
+}
+
+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,
+                                                      _myspace_status_update_update_status);
+}
diff --git a/services/myspace/myspace.h b/services/myspace/myspace.h
new file mode 100644
index 0000000..75e1074
--- /dev/null
+++ b/services/myspace/myspace.h
@@ -0,0 +1,59 @@
+/*
+ * libsocialweb MySpace service support
+ * Copyright (C) 2008 - 2009 Intel Corporation.
+ * Copyright (C) 2010 Novell, Inc.
+ *
+ * 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_MYSPACE
+#define _SW_SERVICE_MYSPACE
+
+#include <libsocialweb/sw-service.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_SERVICE_MYSPACE sw_service_myspace_get_type()
+
+#define SW_SERVICE_MYSPACE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_SERVICE_MYSPACE, SwServiceMySpace))
+
+#define SW_SERVICE_MYSPACE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_SERVICE_MYSPACE, SwServiceMySpaceClass))
+
+#define SW_IS_SERVICE_MYSPACE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_SERVICE_MYSPACE))
+
+#define SW_IS_SERVICE_MYSPACE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_SERVICE_MYSPACE))
+
+#define SW_SERVICE_MYSPACE_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_SERVICE_MYSPACE, SwServiceMySpaceClass))
+
+typedef struct _SwServiceMySpacePrivate SwServiceMySpacePrivate;
+
+typedef struct {
+  SwService parent;
+  SwServiceMySpacePrivate *priv;
+} SwServiceMySpace;
+
+typedef struct {
+  SwServiceClass parent_class;
+} SwServiceMySpaceClass;
+
+GType sw_service_myspace_get_type (void);
+
+G_END_DECLS
+
+#endif /* _SW_SERVICE_MYSPACE */
diff --git a/services/myspace/myspace.keys.in b/services/myspace/myspace.keys.in
new file mode 100644
index 0000000..19a907f
--- /dev/null
+++ b/services/myspace/myspace.keys.in
@@ -0,0 +1,12 @@
+[LibSocialWebService]
+_Name=MySpace
+_Description=MySpace is a leading social portal for connecting people, content, and culture from around the world. Create a profile, share photos, professional and viral videos, blog, instant message your friends, and listen to music on the worldâ??s largest music community.
+Link=http://www.myspace.com/
+AuthType=oauth
+
+[OAuth]
+BaseURL=http://api.myspace.com/
+RequestTokenFunction=request_token
+AuthoriseFunction=authorize
+AccessTokenFunction=access_token
+Callback=http://www.novell.com/
diff --git a/services/myspace/myspace.png b/services/myspace/myspace.png
new file mode 100644
index 0000000..f8e1649
Binary files /dev/null and b/services/myspace/myspace.png differ



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