[libsocialweb] Add Youtube service



commit eb8a51071cce18448f513b5334b454f5ca7e064c
Author: Gary Ching-Pang Lin <chingpang gmail com>
Date:   Thu Jan 13 16:14:39 2011 +0800

    Add Youtube service

 configure.ac                         |    2 +
 services/Makefile.am                 |    4 +
 services/youtube/Makefile.am         |   25 ++
 services/youtube/module.c            |   35 ++
 services/youtube/youtube-item-view.c |  644 ++++++++++++++++++++++++++++++++++
 services/youtube/youtube-item-view.h |   59 +++
 services/youtube/youtube.c           |  454 ++++++++++++++++++++++++
 services/youtube/youtube.h           |   63 ++++
 services/youtube/youtube.keys.in     |    6 +
 services/youtube/youtube.png         |  Bin 0 -> 2465 bytes
 10 files changed, 1292 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 104a95b..567912c 100644
--- a/configure.ac
+++ b/configure.ac
@@ -132,6 +132,7 @@ SOCIALWEB_ENABLE_SERVICE(Plurk, plurk, PLURK)
 SOCIALWEB_ENABLE_SERVICE(SmugMug, smugmug, SMUGMUG)
 SOCIALWEB_ENABLE_SERVICE(Twitter, twitter, TWITTER)
 SOCIALWEB_ENABLE_SERVICE(Vimeo, vimeo, VIMEO)
+SOCIALWEB_ENABLE_SERVICE(Youtube, youtube, YOUTUBE)
 
 servicesdir='${libdir}'/libsocialweb/services
 AC_SUBST(servicesdir)
@@ -190,6 +191,7 @@ AC_OUTPUT([
 	services/smugmug/Makefile
         services/twitter/Makefile
         services/vimeo/Makefile
+        services/youtube/Makefile
         tests/Makefile
         tools/Makefile
         examples/Makefile
diff --git a/services/Makefile.am b/services/Makefile.am
index 2a30d7b..9471ae1 100644
--- a/services/Makefile.am
+++ b/services/Makefile.am
@@ -27,3 +27,7 @@ endif
 if WITH_VIMEO
 SUBDIRS += vimeo
 endif
+
+if WITH_YOUTUBE
+SUBDIRS += youtube
+endif
diff --git a/services/youtube/Makefile.am b/services/youtube/Makefile.am
new file mode 100644
index 0000000..8303a9a
--- /dev/null
+++ b/services/youtube/Makefile.am
@@ -0,0 +1,25 @@
+services_LTLIBRARIES = libyoutube.la
+libyoutube_la_SOURCES = module.c \
+			youtube.c \
+		        youtube.h \
+			youtube-item-view.h \
+		        youtube-item-view.c
+libyoutube_la_CFLAGS = -I$(top_srcdir) \
+		       $(REST_CFLAGS) \
+		       $(KEYRING_CFLAGS) \
+		       $(DBUS_GLIB_CFLAGS) \
+		       -I$(top_srcdir)/utils -DG_LOG_DOMAIN=\"Youtube\"
+libyoutube_la_LIBADD = $(top_builddir)/libsocialweb/libsocialweb.la \
+		       $(top_builddir)/libsocialweb-keystore/libsocialweb-keystore.la \
+		       $(top_builddir)/libsocialweb-keyfob/libsocialweb-keyfob.la \
+		       $(REST_LIBS) \
+		       $(KEYRING_LIBS) \
+		       $(DBUS_GLIB_LIBS)
+libyoutube_la_LDFLAGS = -module -avoid-version
+
+dist_servicesdata_DATA = youtube.png
+
+servicesdata_DATA = youtube.keys
+CLEANFILES = youtube.keys
+EXTRA_DIST = youtube.keys.in
+ INTLTOOL_SOCIALWEB_KEYS@
diff --git a/services/youtube/module.c b/services/youtube/module.c
new file mode 100644
index 0000000..632d755
--- /dev/null
+++ b/services/youtube/module.c
@@ -0,0 +1,35 @@
+/*
+ * libsocialweb Youtube 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 "youtube.h"
+
+const gchar *
+sw_module_get_name (void)
+{
+  return "youtube";
+}
+
+const GType
+sw_module_get_type (void)
+{
+  return SW_TYPE_SERVICE_YOUTUBE;
+}
diff --git a/services/youtube/youtube-item-view.c b/services/youtube/youtube-item-view.c
new file mode 100644
index 0000000..ebd90de
--- /dev/null
+++ b/services/youtube/youtube-item-view.c
@@ -0,0 +1,644 @@
+/*
+ * libsocialweb Youtube 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 <config.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <libsocialweb/sw-utils.h>
+
+#include <rest/rest-proxy.h>
+#include <rest/rest-xml-parser.h>
+#include <libsoup/soup.h>
+
+#include <libsocialweb/sw-debug.h>
+#include <libsocialweb/sw-item.h>
+#include <libsocialweb/sw-cache.h>
+
+#include <glib/gi18n.h>
+
+#include "youtube-item-view.h"
+#include "youtube.h"
+
+G_DEFINE_TYPE (SwYoutubeItemView, sw_youtube_item_view, SW_TYPE_ITEM_VIEW)
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_YOUTUBE_ITEM_VIEW, SwYoutubeItemViewPrivate))
+
+typedef struct _SwYoutubeItemViewPrivate SwYoutubeItemViewPrivate;
+
+struct _SwYoutubeItemViewPrivate {
+  guint timeout_id;
+  GHashTable *params;
+  gchar *query;
+  RestProxy *proxy;
+  gchar *developer_key;
+
+  SwSet *set;
+  GHashTable *thumb_map;
+};
+
+enum
+{
+  PROP_0,
+  PROP_PROXY,
+  PROP_PARAMS,
+  PROP_QUERY,
+  PROP_DEVKEY
+};
+
+#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_youtube_item_view_get_property (GObject    *object,
+                                   guint       property_id,
+                                   GValue     *value,
+                                   GParamSpec *pspec)
+{
+  SwYoutubeItemViewPrivate *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;
+    case PROP_DEVKEY:
+      g_value_set_string (value, priv->developer_key);
+      break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+sw_youtube_item_view_set_property (GObject      *object,
+                                  guint         property_id,
+                                  const GValue *value,
+                                  GParamSpec   *pspec)
+{
+  SwYoutubeItemViewPrivate *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;
+    case PROP_DEVKEY:
+      priv->developer_key = g_value_dup_string (value);
+      break;
+  default:
+    G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+  }
+}
+
+static void
+sw_youtube_item_view_dispose (GObject *object)
+{
+  SwItemView *item_view = SW_ITEM_VIEW (object);
+  SwYoutubeItemViewPrivate *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_youtube_item_view_parent_class)->dispose (object);
+}
+
+static void
+sw_youtube_item_view_finalize (GObject *object)
+{
+  SwYoutubeItemViewPrivate *priv = GET_PRIVATE (object);
+
+  g_free (priv->query);
+  g_hash_table_unref (priv->params);
+  g_hash_table_unref (priv->thumb_map);
+
+  G_OBJECT_CLASS (sw_youtube_item_view_parent_class)->finalize (object);
+}
+
+static RestXmlNode *
+xml_node_from_call (RestProxyCall *call,
+                    const char    *name)
+{
+  static RestXmlParser *parser = NULL;
+  RestXmlNode *root;
+
+  if (call == NULL)
+    return NULL;
+
+  if (parser == NULL)
+    parser = rest_xml_parser_new ();
+
+  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));
+    return NULL;
+  }
+
+  root = rest_xml_parser_parse_from_data (parser,
+                                          rest_proxy_call_get_payload (call),
+                                          rest_proxy_call_get_payload_length (call));
+
+  if (root == NULL) {
+    g_message ("Error from %s: %s",
+               name,
+               rest_proxy_call_get_payload (call));
+    return NULL;
+  }
+
+  /* Exception handling */
+  if (strcmp (name, "Youtube") == 0) {
+    if (strcmp (root->name, "error_response") == 0) {
+      RestXmlNode *node;
+      node = rest_xml_node_find (root, "error_msg");
+      g_message ("Error response from Youtube: %s\n", node->content);
+      rest_xml_node_unref (root);
+      return NULL;
+    }
+  }
+
+  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.
+ */
+static char *
+xml_get_child_node_value (RestXmlNode *node,
+                          const char  *name)
+{
+  RestXmlNode *subnode;
+
+  g_assert (node);
+  g_assert (name);
+
+  subnode = rest_xml_node_find (node, name);
+  if (!subnode)
+    return NULL;
+
+  if (subnode->content && subnode->content[0])
+    return g_strdup (subnode->content);
+  else
+    return NULL;
+}
+
+static char *
+get_author_icon_url (SwYoutubeItemView *youtube, const char *author)
+{
+  SwYoutubeItemViewPrivate *priv = GET_PRIVATE (youtube);
+  RestProxyCall *call;
+  RestXmlNode *root, *node;
+  char *function, *url;
+
+  url = g_hash_table_lookup (priv->thumb_map, author);
+  if (url)
+    return g_strdup (url);
+
+  call = rest_proxy_new_call (priv->proxy);
+  function = g_strdup_printf ("users/%s", author);
+  rest_proxy_call_set_function (call, function);
+  rest_proxy_call_sync (call, NULL);
+
+  root = xml_node_from_call (call, "Youtube");
+  if (!root)
+    return NULL;
+
+  node = rest_xml_node_find (root, "media:thumbnail");
+  if (!node)
+    return NULL;
+
+  url = g_strdup (rest_xml_node_get_attr (node, "url"));
+
+  g_free (function);
+
+  if (url)
+    g_hash_table_insert(priv->thumb_map, (gpointer)author, (gpointer)g_strdup (url));
+
+  return url;
+}
+
+static char *
+get_utc_date (const char *s)
+{
+  struct tm tm;
+
+  if (s == NULL)
+    return NULL;
+
+  strptime (s, "%Y-%m-%dT%T", &tm);
+
+  return sw_time_t_to_string (mktime (&tm));
+}
+
+static SwItem *
+make_item (SwYoutubeItemView *item_view,
+           SwService        *service,
+           RestXmlNode      *node)
+{
+  SwItem *item;
+  char *author, *date, *url;
+  RestXmlNode *subnode, *thumb_node;
+
+  item = sw_item_new ();
+  sw_item_set_service (item, service);
+  /*
+  <rss>
+    <channel>
+      <item>
+        <guid isPermaLink="false">http://gdata.youtube.com/feeds/api/videos/<videoid></guid>
+        <atom:updated>2010-02-13T06:17:32.000Z</atom:updated>
+        <title>Video Title</title>
+        <author>Author Name</author>
+        <link>http://www.youtube.com/watch?v=<videoid>&amp;feature=youtube_gdata</link>
+        <media:group>
+          <media:thumbnail url="http://i.ytimg.com/vi/<videoid>/default.jpg" height="90" width="120" time="00:03:00.500"/>
+        </media:group>
+      </item>
+    </channel>
+  </rss>
+  */
+
+  sw_item_put (item, "id", xml_get_child_node_value (node, "guid"));
+
+  date = xml_get_child_node_value (node, "atom:updated");
+  if (date != NULL)
+    sw_item_put (item, "date", get_utc_date(date));
+
+  sw_item_put (item, "title", xml_get_child_node_value (node, "title"));
+  sw_item_put (item, "url", xml_get_child_node_value (node, "link"));
+  author = xml_get_child_node_value (node, "author");
+  sw_item_put (item, "author", author);
+
+  /* media:group */
+  subnode = rest_xml_node_find (node, "media:group");
+  if (subnode){
+    thumb_node = rest_xml_node_find (subnode, "media:thumbnail");
+    url = (char *) rest_xml_node_get_attr (thumb_node, "url");
+    sw_item_request_image_fetch (item, TRUE, "thumbnail", url);
+  }
+
+  url = get_author_icon_url (item_view, author);
+  sw_item_request_image_fetch (item, FALSE, "authoricon", url);
+  g_free (url);
+
+  return item;
+}
+
+static void
+_got_videos_cb (RestProxyCall *call,
+                const GError  *error,
+                GObject       *weak_object,
+                gpointer       user_data)
+{
+  SwYoutubeItemView *item_view = SW_YOUTUBE_ITEM_VIEW (weak_object);
+  SwYoutubeItemViewPrivate *priv = GET_PRIVATE (item_view);
+  SwService *service;
+  RestXmlNode *root, *node;
+
+  if (error) {
+    g_message (G_STRLOC ": error from Youtube: %s", error->message);
+    /* TODO: cleanup or something */
+    return;
+  }
+
+  root = xml_node_from_call (call, "Youtube");
+  if (!root)
+    return;
+
+  node = rest_xml_node_find (root, "channel");
+  if (!node)
+    return;
+
+  /* Clean up the thumbnail mapping cache */
+  g_hash_table_remove_all (priv->thumb_map);
+
+  service = sw_item_view_get_service (SW_ITEM_VIEW (item_view));
+
+  for (node = rest_xml_node_find (node, "item"); node; node = node->next) {
+    SwItem *item;
+    item = make_item (item_view, service, node);
+
+    if (!sw_service_is_uid_banned (service, sw_item_get (item, "id"))) {
+      sw_set_add (priv->set, (GObject *)item);
+    }
+
+    g_object_unref (item);
+  }
+
+  sw_item_view_set_from_set ((SwItemView *)item_view, priv->set);
+
+  /* Save the results of this set to the cache */
+  sw_cache_save (service,
+                 priv->query,
+                 priv->params,
+                 priv->set);
+
+  sw_set_empty (priv->set);
+
+  rest_xml_node_unref (root);
+}
+
+static void
+_get_status_updates (SwYoutubeItemView *item_view)
+{
+  SwYoutubeItemViewPrivate *priv = GET_PRIVATE (item_view);
+  SwService *service = sw_item_view_get_service ((SwItemView *)item_view);
+  RestProxyCall *call;
+  char *user_auth_header = NULL, *devkey_header = NULL;
+  const char *user_auth = NULL;
+
+  user_auth = sw_service_youtube_get_user_auth (SW_SERVICE_YOUTUBE (service));
+  if (user_auth == NULL)
+    return;
+
+  sw_set_empty (priv->set);
+
+  call = rest_proxy_new_call (priv->proxy);
+
+  user_auth_header = g_strdup_printf ("GoogleLogin auth=%s", user_auth);
+  rest_proxy_call_add_header (call, "Authorization", user_auth_header);
+  devkey_header = g_strdup_printf ("key=%s", priv->developer_key);
+  rest_proxy_call_add_header (call, "X-GData-Key", devkey_header);
+
+  if (g_str_equal (priv->query, "feed"))
+    rest_proxy_call_set_function (call, "users/default/newsubscriptionvideos");
+  else if (g_str_equal (priv->query, "own"))
+    rest_proxy_call_set_function (call, "users/default/uploads");
+  else
+    g_assert_not_reached ();
+
+  rest_proxy_call_add_params (call,
+                              "max-results", "10",
+                              "alt", "rss",
+                              NULL);
+
+  rest_proxy_call_async (call,
+                         _got_videos_cb,
+                         (GObject *)item_view,
+                         NULL,
+                         NULL);
+  g_free (user_auth_header);
+  g_free (devkey_header);
+}
+
+
+static gboolean
+_update_timeout_cb (gpointer data)
+{
+  SwYoutubeItemView *item_view = SW_YOUTUBE_ITEM_VIEW (data);
+
+  _get_status_updates (item_view);
+
+  return TRUE;
+}
+
+static void
+_load_from_cache (SwYoutubeItemView *item_view)
+{
+  SwYoutubeItemViewPrivate *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);
+
+  if (set)
+  {
+    sw_item_view_set_from_set (SW_ITEM_VIEW (item_view),
+                               set);
+    sw_set_unref (set);
+  }
+}
+
+static void
+youtube_item_view_start (SwItemView *item_view)
+{
+  SwYoutubeItemViewPrivate *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 ((SwYoutubeItemView *)item_view);
+    _get_status_updates ((SwYoutubeItemView *)item_view);
+  }
+}
+
+static void
+youtube_item_view_stop (SwItemView *item_view)
+{
+  SwYoutubeItemViewPrivate *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
+youtube_item_view_refresh (SwItemView *item_view)
+{
+  _get_status_updates ((SwYoutubeItemView *)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)
+{
+  SwYoutubeItemViewPrivate *priv = GET_PRIVATE ((SwYoutubeItemView*) item_view);
+
+  if (sw_service_has_cap (caps, CREDENTIALS_VALID))
+  {
+    youtube_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_youtube_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_youtube_item_view_parent_class)->constructed)
+    G_OBJECT_CLASS (sw_youtube_item_view_parent_class)->constructed (object);
+}
+
+static void
+sw_youtube_item_view_class_init (SwYoutubeItemViewClass *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 (SwYoutubeItemViewPrivate));
+
+  object_class->get_property = sw_youtube_item_view_get_property;
+  object_class->set_property = sw_youtube_item_view_set_property;
+  object_class->dispose = sw_youtube_item_view_dispose;
+  object_class->finalize = sw_youtube_item_view_finalize;
+  object_class->constructed = sw_youtube_item_view_constructed;
+
+  item_view_class->start = youtube_item_view_start;
+  item_view_class->stop = youtube_item_view_stop;
+  item_view_class->refresh = youtube_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);
+
+
+  pspec = g_param_spec_string ("developer_key",
+                               "developer_key",
+                               "developer_key",
+                               NULL,
+                               G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY);
+  g_object_class_install_property (object_class, PROP_DEVKEY, pspec);
+}
+
+static void
+sw_youtube_item_view_init (SwYoutubeItemView *self)
+{
+  SwYoutubeItemViewPrivate *priv = GET_PRIVATE (self);
+
+  priv->set = sw_item_set_new ();
+  priv->thumb_map = g_hash_table_new(g_str_hash, g_str_equal);
+}
diff --git a/services/youtube/youtube-item-view.h b/services/youtube/youtube-item-view.h
new file mode 100644
index 0000000..447e476
--- /dev/null
+++ b/services/youtube/youtube-item-view.h
@@ -0,0 +1,59 @@
+/*
+ * libsocialweb Youtube 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_YOUTUBE_ITEM_VIEW
+#define _SW_YOUTUBE_ITEM_VIEW
+
+#include <glib-object.h>
+#include <libsocialweb/sw-item-view.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_YOUTUBE_ITEM_VIEW sw_youtube_item_view_get_type()
+
+#define SW_YOUTUBE_ITEM_VIEW(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_YOUTUBE_ITEM_VIEW, SwYoutubeItemView))
+
+#define SW_YOUTUBE_ITEM_VIEW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_YOUTUBE_ITEM_VIEW, SwYoutubeItemViewClass))
+
+#define SW_IS_YOUTUBE_ITEM_VIEW(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_YOUTUBE_ITEM_VIEW))
+
+#define SW_IS_YOUTUBE_ITEM_VIEW_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_YOUTUBE_ITEM_VIEW))
+
+#define SW_YOUTUBE_ITEM_VIEW_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_YOUTUBE_ITEM_VIEW, SwYoutubeItemViewClass))
+
+typedef struct {
+  SwItemView parent;
+} SwYoutubeItemView;
+
+typedef struct {
+  SwItemViewClass parent_class;
+} SwYoutubeItemViewClass;
+
+GType sw_youtube_item_view_get_type (void);
+
+G_END_DECLS
+
+#endif /* _SW_YOUTUBE_ITEM_VIEW */
diff --git a/services/youtube/youtube.c b/services/youtube/youtube.c
new file mode 100644
index 0000000..155cb44
--- /dev/null
+++ b/services/youtube/youtube.c
@@ -0,0 +1,454 @@
+/*
+ * libsocialweb Youtube 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 <config.h>
+#include <time.h>
+#include <stdlib.h>
+#include <string.h>
+#include <glib/gi18n.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <gnome-keyring.h>
+
+#include <libsocialweb/sw-item.h>
+#include <libsocialweb/sw-set.h>
+#include <libsocialweb/sw-online.h>
+#include <libsocialweb/sw-utils.h>
+#include <libsocialweb/sw-web.h>
+#include <libsocialweb/sw-debug.h>
+#include <libsocialweb-keyfob/sw-keyfob.h>
+#include <libsocialweb-keystore/sw-keystore.h>
+#include <libsocialweb/sw-client-monitor.h>
+
+#include <rest/rest-proxy.h>
+#include <rest/rest-xml-parser.h>
+
+#include <interfaces/sw-query-ginterface.h>
+
+#include "youtube.h"
+#include "youtube-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);
+
+G_DEFINE_TYPE_WITH_CODE (SwServiceYoutube,
+                         sw_service_youtube,
+                         SW_TYPE_SERVICE,
+                         G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE,
+                                                initable_iface_init)
+                         G_IMPLEMENT_INTERFACE (SW_TYPE_QUERY_IFACE,
+                                                query_iface_init));
+
+#define GET_PRIVATE(o) \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o), SW_TYPE_SERVICE_YOUTUBE, SwServiceYoutubePrivate))
+
+struct _SwServiceYoutubePrivate {
+  gboolean inited;
+  enum {
+    OFFLINE,
+    CREDS_INVALID,
+    CREDS_VALID
+  } credentials;
+  RestProxy *proxy;
+  RestProxy *auth_proxy;
+  char *username;
+  char *password;
+  char *developer_key;
+  char *user_auth;
+  char *nickname;
+};
+
+static void online_notify (gboolean online, gpointer user_data);
+static void credentials_updated (SwService *service);
+
+static const char **
+get_static_caps (SwService *service)
+{
+  static const char * caps[] = {
+    CAN_VERIFY_CREDENTIALS,
+    HAS_QUERY_IFACE,
+    HAS_BANISHABLE_IFACE,
+
+    NULL
+  };
+
+  return caps;
+}
+
+static const char **
+get_dynamic_caps (SwService *service)
+{
+  SwServiceYoutubePrivate *priv = GET_PRIVATE (service);
+  static const char *no_caps[] = { NULL };
+  static const char *configured_caps[] = {
+    IS_CONFIGURED,
+    NULL
+  };
+  static const char *invalid_caps[] = {
+    IS_CONFIGURED,
+    CREDENTIALS_INVALID,
+    NULL
+  };
+  static const char *full_caps[] = {
+    IS_CONFIGURED,
+    CREDENTIALS_VALID,
+    NULL
+  };
+
+  switch (priv->credentials) {
+  case CREDS_VALID:
+    return full_caps;
+  case CREDS_INVALID:
+    return invalid_caps;
+  case OFFLINE:
+    if (priv->username && priv->password)
+      return configured_caps;
+    else
+      return no_caps;
+  }
+
+  /* Just in case we fell through that switch */
+  g_warning ("Unhandled credential state %d", priv->credentials);
+  return no_caps;
+}
+
+const char *
+sw_service_youtube_get_user_auth (SwServiceYoutube *youtube)
+{
+  SwServiceYoutubePrivate *priv = GET_PRIVATE (youtube);
+
+  return priv->user_auth;
+}
+
+static void
+_got_user_auth (RestProxyCall *call,
+                const GError  *error,
+                GObject       *weak_object,
+                gpointer       user_data)
+{
+  SwService *service = SW_SERVICE (weak_object);
+  SwServiceYoutubePrivate *priv = SW_SERVICE_YOUTUBE (service)->priv;
+  const char *message = rest_proxy_call_get_payload (call);
+  char **tokens;
+
+  if (error) {
+    g_message ("Error: %s", error->message);
+    g_message ("Error from Youtube: %s", message);
+    priv->credentials = CREDS_INVALID;
+    sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+    return;
+  }
+
+  /* Parse the message */
+  tokens = g_strsplit_set (message, "=\n", -1);
+  if (g_strcmp0 (tokens[0], "Auth") == 0 &&
+      g_strcmp0 (tokens[2], "YouTubeUser") == 0) {
+    priv->user_auth = g_strdup (tokens[1]);
+    /*Alright! we got the auth!!!*/
+    priv->nickname = g_strdup (tokens[3]);
+    priv->credentials = CREDS_VALID;
+  } else {
+    priv->credentials = CREDS_INVALID;
+  }
+
+  g_strfreev(tokens);
+
+  sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+
+  g_object_unref (call);
+}
+
+static void
+online_notify (gboolean online, gpointer user_data)
+{
+  SwServiceYoutube *youtube = (SwServiceYoutube *)user_data;
+  SwServiceYoutubePrivate *priv = GET_PRIVATE (youtube);
+
+  priv->credentials = OFFLINE;
+
+  if (online) {
+    if (priv->username && priv->password) {
+      RestProxyCall *call;
+
+      /* request user_auth */
+      /* http://code.google.com/intl/zh-TW/apis/youtube/2.0/developers_guide_protocol_clientlogin.html */
+      call = rest_proxy_new_call (priv->auth_proxy);
+      rest_proxy_call_set_method (call, "POST");
+      rest_proxy_call_set_function (call, "ClientLogin");
+      rest_proxy_call_add_params (call,
+                                  "Email", priv->username,
+                                  "Passwd", priv->password,
+                                  "service", "youtube",
+                                  "source", "SUSE MeeGo",
+                                  NULL);
+      rest_proxy_call_add_header (call,
+                                  "Content-Type",
+                                  "application/x-www-form-urlencoded");
+      rest_proxy_call_async (call,
+                             (RestProxyCallAsyncCallback)_got_user_auth,
+                             (GObject*)youtube,
+                             NULL,
+                             NULL);
+    }
+  } else {
+    sw_service_emit_capabilities_changed ((SwService *)youtube,
+                                          get_dynamic_caps ((SwService *)youtube));
+  }
+}
+
+/*
+ * Callback from the keyring lookup in refresh_credentials.
+ */
+static void
+found_password_cb (GnomeKeyringResult  result,
+                   GList              *list,
+                   gpointer            user_data)
+{
+  SwService *service = SW_SERVICE (user_data);
+  SwServiceYoutube *youtube = SW_SERVICE_YOUTUBE (service);
+  SwServiceYoutubePrivate *priv = GET_PRIVATE (youtube);
+
+  if (result == GNOME_KEYRING_RESULT_OK && list != NULL) {
+    GnomeKeyringNetworkPasswordData *data = list->data;
+
+    g_free (priv->username);
+    g_free (priv->password);
+
+    priv->username = g_strdup (data->user);
+    priv->password = g_strdup (data->password);
+
+    /* If we're online, force a reconnect to fetch new credentials */
+    if (sw_is_online ()) {
+      online_notify (FALSE, service);
+      online_notify (TRUE, service);
+    }
+  } else {
+    g_free (priv->username);
+    g_free (priv->password);
+    priv->username = NULL;
+    priv->password = NULL;
+    priv->credentials = OFFLINE;
+
+    if (result != GNOME_KEYRING_RESULT_NO_MATCH) {
+      g_warning (G_STRLOC ": Error getting password: %s", gnome_keyring_result_to_message (result));
+    }
+  }
+
+  sw_service_emit_user_changed (service);
+  sw_service_emit_capabilities_changed (service, get_dynamic_caps (service));
+}
+
+/*
+ * The credentials have been updated (or we're starting up), so fetch them from
+ * the keyring.
+ */
+static void
+refresh_credentials (SwServiceYoutube *youtube)
+{
+  gnome_keyring_find_network_password (NULL, NULL,
+                                       "www.youtube.com",
+                                       NULL, NULL, NULL, 0,
+                                       found_password_cb, youtube, NULL);
+}
+
+static void
+credentials_updated (SwService *service)
+{
+  refresh_credentials (SW_SERVICE_YOUTUBE (service));
+}
+
+static const char *
+sw_service_youtube_get_name (SwService *service)
+{
+  return "youtube";
+}
+
+static void
+sw_service_youtube_dispose (GObject *object)
+{
+  SwServiceYoutubePrivate *priv = SW_SERVICE_YOUTUBE (object)->priv;
+
+  sw_online_remove_notify (online_notify, object);
+
+  if (priv->proxy) {
+    g_object_unref (priv->proxy);
+    priv->proxy = NULL;
+  }
+
+  if (priv->auth_proxy) {
+    g_object_unref (priv->auth_proxy);
+    priv->auth_proxy = NULL;
+  }
+
+  G_OBJECT_CLASS (sw_service_youtube_parent_class)->dispose (object);
+}
+
+static void
+sw_service_youtube_finalize (GObject *object)
+{
+  SwServiceYoutubePrivate *priv = SW_SERVICE_YOUTUBE (object)->priv;
+
+  g_free (priv->username);
+  g_free (priv->password);
+  g_free (priv->user_auth);
+  g_free (priv->developer_key);
+  g_free (priv->nickname);
+
+  G_OBJECT_CLASS (sw_service_youtube_parent_class)->finalize (object);
+}
+
+static void
+sw_service_youtube_class_init (SwServiceYoutubeClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  SwServiceClass *service_class = SW_SERVICE_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (SwServiceYoutubePrivate));
+
+  object_class->dispose = sw_service_youtube_dispose;
+  object_class->finalize = sw_service_youtube_finalize;
+
+  service_class->get_name = sw_service_youtube_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_youtube_init (SwServiceYoutube *self)
+{
+  self->priv = GET_PRIVATE (self);
+
+  self->priv->inited = FALSE;
+  self->priv->username = NULL;
+  self->priv->password = NULL;
+  self->priv->user_auth = NULL;
+  self->priv->nickname = NULL;
+}
+
+/* Initable interface */
+
+static gboolean
+sw_service_youtube_initable (GInitable    *initable,
+                             GCancellable *cancellable,
+                             GError      **error)
+{
+  SwServiceYoutube *youtube = SW_SERVICE_YOUTUBE (initable);
+  SwServiceYoutubePrivate *priv = GET_PRIVATE (youtube);
+  const char *key = NULL;
+
+  if (priv->inited)
+    return TRUE;
+
+  sw_keystore_get_key_secret ("youtube", &key, NULL);
+  if (key == NULL) {
+    g_set_error_literal (error,
+                         SW_SERVICE_ERROR,
+                         SW_SERVICE_ERROR_NO_KEYS,
+                         "No API key configured");
+    return FALSE;
+  }
+
+  priv->proxy = rest_proxy_new ("http://gdata.youtube.com/feeds/api/";, FALSE);
+  priv->auth_proxy = rest_proxy_new ("https://www.google.com/youtube/accounts/";, FALSE);
+
+  priv->developer_key = (char *)key;
+  priv->credentials = OFFLINE;
+
+  sw_online_add_notify (online_notify, youtube);
+
+  refresh_credentials (youtube);
+
+  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_youtube_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
+_youtube_query_open_view (SwQueryIface          *self,
+                          const gchar           *query,
+                          GHashTable            *params,
+                          DBusGMethodInvocation *context)
+{
+  SwServiceYoutubePrivate *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_YOUTUBE_ITEM_VIEW,
+                            "proxy", priv->proxy,
+                            "developer_key", priv->developer_key,
+                            "service", self,
+                            "query", query,
+                            "params", params,
+                            NULL);
+
+  /* Ensure the object gets disposed when the client goes away */
+  sw_client_monitor_add (dbus_g_method_get_sender (context),
+                         (GObject *)item_view);
+
+
+  object_path = sw_item_view_get_object_path (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, _youtube_query_open_view);
+}
diff --git a/services/youtube/youtube.h b/services/youtube/youtube.h
new file mode 100644
index 0000000..42d2ba4
--- /dev/null
+++ b/services/youtube/youtube.h
@@ -0,0 +1,63 @@
+/*
+ * libsocialweb Youtube 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_YOUTUBE
+#define _SW_SERVICE_YOUTUBE
+
+#include <libsocialweb/sw-service.h>
+
+G_BEGIN_DECLS
+
+#define SW_TYPE_SERVICE_YOUTUBE sw_service_youtube_get_type()
+
+#define SW_SERVICE_YOUTUBE(obj) \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj), SW_TYPE_SERVICE_YOUTUBE, SwServiceYoutube))
+
+#define SW_SERVICE_YOUTUBE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_CAST ((klass), SW_TYPE_SERVICE_YOUTUBE, SwServiceYoutubeClass))
+
+#define SW_IS_SERVICE_YOUTUBE(obj) \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj), SW_TYPE_SERVICE_YOUTUBE))
+
+#define SW_IS_SERVICE_YOUTUBE_CLASS(klass) \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass), SW_TYPE_SERVICE_YOUTUBE))
+
+#define SW_SERVICE_YOUTUBE_GET_CLASS(obj) \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj), SW_TYPE_SERVICE_YOUTUBE, SwServiceYoutubeClass))
+
+typedef struct _SwServiceYoutubePrivate SwServiceYoutubePrivate;
+
+typedef struct {
+  SwService parent;
+  SwServiceYoutubePrivate *priv;
+} SwServiceYoutube;
+
+typedef struct {
+  SwServiceClass parent_class;
+} SwServiceYoutubeClass;
+
+GType sw_service_youtube_get_type (void);
+
+const char* sw_service_youtube_get_user_auth (SwServiceYoutube *youtube);
+
+G_END_DECLS
+
+#endif /* _SW_SERVICE_YOUTUBE */
diff --git a/services/youtube/youtube.keys.in b/services/youtube/youtube.keys.in
new file mode 100644
index 0000000..bcf916c
--- /dev/null
+++ b/services/youtube/youtube.keys.in
@@ -0,0 +1,6 @@
+[LibSocialWebService]
+_Name=Youtube
+_Description=Youtube is a popular video sharing website
+Link=http://www.youtube.com
+AuthType=password
+AuthPasswordServer=www.youtube.com
diff --git a/services/youtube/youtube.png b/services/youtube/youtube.png
new file mode 100644
index 0000000..60831e9
Binary files /dev/null and b/services/youtube/youtube.png differ



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