[grilo-plugins] Raitv: Add Rai.tv plugin



commit 74284713b91cb1c0307a4bc3cdd71926b9ac5569
Author: Marco Piazza <mpiazza gmail com>
Date:   Sun Dec 2 23:56:43 2012 +0100

    Raitv: Add Rai.tv plugin
    
    Retrieves movies information from Rai website (www.rai.tv).
    
    RAI, or Radio Televisione Italiana, founded in 1954
    is the Italian state owned public service broadcaster.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=609333
    
    Signed-off-by: Juan A. Suarez Romero <jasuarez igalia com>

 configure.ac            |   44 ++
 src/Makefile.am         |    4 +
 src/raitv/Makefile.am   |   34 ++
 src/raitv/grl-raitv.c   | 1264 +++++++++++++++++++++++++++++++++++++++++++++++
 src/raitv/grl-raitv.h   |   76 +++
 src/raitv/grl-raitv.xml |   10 +
 6 files changed, 1432 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 3fd65dc..bf6814f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -864,6 +864,49 @@ DEPS_BLIPTV_LIBS="$DEPS_LIBS $GRLNET_LIBS $XML_LIBS"
 AC_SUBST(DEPS_BLIPTV_LIBS)
 
 # ----------------------------------------------------------
+# BUILD RAI.TV PLUGIN
+# ----------------------------------------------------------
+
+AC_ARG_ENABLE(raitv,
+        AC_HELP_STRING([--enable-raitv],
+                [enable Rai.tv plugin (default: auto)]),
+        [
+                case "$enableval" in
+                     yes)
+                        if test "x$HAVE_GRLNET" = "xno"; then
+                           AC_MSG_ERROR([grilo-net not found, install it or use --disable-raitv])
+                        fi
+                        if test "x$HAVE_XML" = "xno"; then
+                           AC_MSG_ERROR([xml2 not found, install it or use --disable-raitv])
+                        fi
+                        ;;
+                esac
+        ],
+        [
+                if test "x$HAVE_GRLNET" = "xyes" -a "x$HAVE_XML" = "xyes"; then
+                   enable_raitv=yes
+                else
+                   enable_raitv=no
+                fi
+        ])
+
+AM_CONDITIONAL([RAITV_PLUGIN], [test "x$enable_raitv" = "xyes"])
+GRL_PLUGINS_ALL="$GRL_PLUGINS_ALL rai.tv"
+if test "x$enable_raitv" = "xyes"
+then
+	GRL_PLUGINS_ENABLED="$GRL_PLUGINS_ENABLED rai.tv"
+fi
+
+RAITV_PLUGIN_ID="grl-raitv"
+AC_SUBST(RAITV_PLUGIN_ID)
+AC_DEFINE_UNQUOTED([RAITV_PLUGIN_ID], ["$RAITV_PLUGIN_ID"], [Rai.tv plugin ID])
+
+DEPS_RAITV_CFLAGS="$DEPS_CFLAGS $LIBXML_CFLAGS $GRLNET_CFLAGS"
+AC_SUBST(DEPS_RAITV_CFLAGS)
+DEPS_RAITV_LIBS="$DEPS_LIBS $LIBXML_LIBS $GRLNET_LIBS"
+AC_SUBST(DEPS_RAITV_LIBS)
+
+# ----------------------------------------------------------
 # BUILD LOCAL METADATA PLUGIN
 # ----------------------------------------------------------
 
@@ -1022,6 +1065,7 @@ AC_CONFIG_FILES([
   src/metadata-store/Makefile
   src/optical-media/Makefile
   src/podcasts/Makefile
+  src/raitv/Makefile
   src/shoutcast/Makefile
   src/tmdb/Makefile
   src/tracker/Makefile
diff --git a/src/Makefile.am b/src/Makefile.am
index 0078e85..6cd027d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -64,6 +64,10 @@ if PODCASTS_PLUGIN
 SUBDIRS += podcasts
 endif
 
+if RAITV_PLUGIN
+SUBDIRS += raitv
+endif
+
 if SHOUTCAST_PLUGIN
 SUBDIRS += shoutcast
 endif
diff --git a/src/raitv/Makefile.am b/src/raitv/Makefile.am
new file mode 100644
index 0000000..70a975f
--- /dev/null
+++ b/src/raitv/Makefile.am
@@ -0,0 +1,34 @@
+#
+# Makefile.am
+#
+# Author: Marco Piazza <mpiazza gmail com>
+#
+
+ext_LTLIBRARIES		 = libgrlraitv.la
+
+libgrlraitv_la_CFLAGS =	\
+	$(DEPS_RAITV_CFLAGS)
+
+libgrlraitv_la_LIBADD =	\
+	$(DEPS_RAITV_LIBS)
+
+libgrlraitv_la_LDFLAGS = \
+	-no-undefined	  \
+	-module		  \
+	-avoid-version
+
+libgrlraitv_la_SOURCES = 		\
+	grl-raitv.c			\
+	grl-raitv.h
+
+extdir 			= $(GRL_PLUGINS_DIR)
+raitvxmldir 	= $(GRL_PLUGINS_DIR)
+raitvxml_DATA	= $(RAITV_PLUGIN_ID).xml
+
+EXTRA_DIST = $(raitvxml_DATA)
+
+MAINTAINERCLEANFILES =	\
+	*.in		\
+	*~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/raitv/grl-raitv.c b/src/raitv/grl-raitv.c
new file mode 100644
index 0000000..0050f20
--- /dev/null
+++ b/src/raitv/grl-raitv.c
@@ -0,0 +1,1264 @@
+/*
+ *
+ * Author: Marco Piazza <mpiazza gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+#include <libxml/xpathInternals.h>
+#include <libxml/HTMLparser.h>
+#include <string.h>
+#include <net/grl-net.h>
+
+#include "grl-raitv.h"
+
+/* --------- Logging  -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT raitv_log_domain
+GRL_LOG_DOMAIN_STATIC(raitv_log_domain);
+
+/* ----- Root categories ---- */
+
+#define RAITV_ROOT_NAME       "Rai.tv"
+
+#define ROOT_DIR_POPULARS_INDEX	0
+#define ROOT_DIR_RECENTS_INDEX 	1
+
+#define RAITV_POPULARS_ID	"most-popular"
+#define RAITV_POPULARS_NAME	"Most Popular"
+
+#define RAITV_RECENTS_ID	"recent"
+#define RAITV_RECENTS_NAME	"Recent"
+
+#define RAITV_POPULARS_THEME_ID	"theme-popular"
+#define RAITV_RECENTS_THEME_ID	"theme-recent"
+
+#define RAITV_VIDEO_SEARCH                      \
+  "http://www.ricerca.rai.it/search?";           \
+  "q=%s"                                        \
+  "&num=50"                                     \
+  "&start=%s"                                   \
+  "&getfields=*"                                \
+  "&site=raitv"                                 \
+  "&filter=0"
+
+
+#define RAITV_VIDEO_POPULAR                        \
+  "http://www.rai.it//StatisticheProxy/proxy.jsp?"; \
+  "action=mostVisited"                             \
+  "&domain=RaiTv"                                  \
+  "&days=7"                                        \
+  "&state=1"                                       \
+  "&records=%s"                                    \
+  "&type=Video"                                    \
+  "&excludeTags=%s"                                \
+  "&tags=%s"
+
+#define RAITV_VIDEO_RECENT                            \
+  "http://www.rai.it/StatisticheProxy/proxyPost.jsp?"; \
+  "action=getLastContentByTag"                        \
+  "&domain=RaiTv"                                     \
+  "&numContents=%s"                                   \
+  "&type=Video"                                       \
+  "&tags=%s"                                          \
+  "&excludeTags=%s"
+
+
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID   RAITV_PLUGIN_ID
+
+#define SOURCE_ID   "grl-raitv"
+#define SOURCE_NAME "Rai.tv"
+#define SOURCE_DESC "A source for browsing and searching Rai.tv videos"
+
+
+G_DEFINE_TYPE (GrlRaitvSource, grl_raitv_source, GRL_TYPE_SOURCE)
+
+#define RAITV_SOURCE_PRIVATE(o)                          \
+  (G_TYPE_INSTANCE_GET_PRIVATE ((o),                     \
+                                GRL_TYPE_RAITV_SOURCE,   \
+                                GrlRaitvSourcePrivate))
+
+typedef enum {
+  RAITV_MEDIA_TYPE_ROOT,
+  RAITV_MEDIA_TYPE_POPULARS,
+  RAITV_MEDIA_TYPE_RECENTS,
+  RAITV_MEDIA_TYPE_POPULAR_THEME,
+  RAITV_MEDIA_TYPE_RECENT_THEME,
+  RAITV_MEDIA_TYPE_VIDEO,
+} RaitvMediaType;
+
+typedef struct {
+  gchar *id;
+  gchar *name;
+  guint count;
+  gchar *tags;
+  gchar *excludeTags;
+} CategoryInfo;
+
+
+typedef struct
+{
+  GrlSource *source;
+  guint      operation_id;
+  const gchar *container_id;
+  guint      count;
+  guint      length;
+  guint      offset;
+  guint      skip;
+
+  GrlSourceResultCb 	callback;
+  GrlSourceResolveCb 	resolveCb;
+  gpointer      user_data;
+  gchar*	text;
+
+  CategoryInfo 	*category_info;
+  GrlMedia	*media;
+
+  GCancellable *cancellable;
+} RaitvOperation;
+
+typedef struct
+{
+  GrlKeyID     grl_key;
+  const gchar *exp;
+} RaitvAssoc;
+
+
+
+
+
+/* ==================== Private Data  ================= */
+
+struct _GrlRaitvSourcePrivate
+{
+  GrlNetWc *wc;
+
+  GList *raitv_search_mappings;
+  GList *raitv_browse_mappings;
+};
+
+
+guint root_dir_size = 2;
+CategoryInfo root_dir[] = {
+  {RAITV_POPULARS_ID,    RAITV_POPULARS_NAME,      23},
+  {RAITV_RECENTS_ID, 	RAITV_RECENTS_NAME,	 23},
+  {NULL, NULL, 0}
+};
+
+CategoryInfo themes_dir[] = {
+  {"all","All",-1,"","Tematica:News^Tematica:ntz"},
+  {"bianco_nero","Bianco e Nero",-1,"Tematica:Bianco e Nero",""},
+  {"cinema","Cinema",-1,"Tematica:Cinema",""},
+  {"comici","Comici",-1,"Tematica:Comici",""},
+  {"cronaca","Cronaca",-1,"Tematica:Cronaca",""},
+  {"cultura","Cultura",-1,"Tematica:Cultura",""},
+  {"economia","Economia",-1,"Tematica:Economia",""},
+  {"fiction","Fiction",-1,"Tematica:Fiction",""},
+  {"junior","Junior",-1,"Tematica:Junior",""},
+  {"inchieste","Inchieste",-1,"Tematica:Inchieste",""},
+  {"interviste","Interviste",-1,"Tematica:Interviste",""},
+  {"musica","Musica",-1,"Tematica:Musica",""},
+  {"news","News",-1,"Tematica:News",""},
+  {"salute","Salute",-1,"Tematica:Salute",""},
+  {"satira","Satira",-1,"Tematica:Satira",""},
+  {"scienza","Scienza",-1,"Tematica:Scienza",""},
+  {"societa","Societa",-1,"Tematica:Societa",""},
+  {"spettacolo","Spettacolo",-1,"Tematica:Spettacolo",""},
+  {"sport","Sport",-1,"Tematica:Sport",""},
+  {"storia","Storia",-1,"Tematica:Storia",""},
+  {"politica","Politica",-1,"Tematica:Politica",""},
+  {"tempo_libero","Tempo libero",-1,"Tematica:Tempo libero",""},
+  {"viaggi","Viaggi",-1,"Tematica:Viaggi",""},
+  {NULL, NULL, 0}
+};
+
+
+/**/
+
+static GrlRaitvSource *grl_raitv_source_new (void);
+
+gboolean grl_raitv_plugin_init (GrlRegistry *registry,
+                                GrlPlugin *plugin,
+                                GList *configs);
+
+static const GList *grl_raitv_source_supported_keys (GrlSource *source);
+static const GList *grl_raitv_source_slow_keys (GrlSource *source);
+
+static void grl_raitv_source_browse (GrlSource *source,
+                                     GrlSourceBrowseSpec *bs);
+
+static void grl_raitv_source_search (GrlSource *source,
+                                     GrlSourceSearchSpec *ss);
+
+static void grl_raitv_source_resolve (GrlSource *source,
+                                      GrlSourceResolveSpec *ss);
+
+static void g_raitv_videos_search(RaitvOperation *op);
+
+static void produce_from_popular_theme (RaitvOperation *op);
+static void produce_from_recent_theme (RaitvOperation *op);
+
+static void grl_raitv_source_cancel (GrlSource *source,
+                                     guint operation_id);
+
+static RaitvMediaType classify_media_id (const gchar *media_id);
+
+
+/**/
+
+gboolean
+grl_raitv_plugin_init (GrlRegistry *registry,
+                       GrlPlugin *plugin,
+                       GList *configs)
+{
+  GRL_LOG_DOMAIN_INIT (raitv_log_domain, "raitv");
+
+  GrlRaitvSource *source = grl_raitv_source_new ();
+  grl_registry_register_source (registry,
+                                plugin,
+                                GRL_SOURCE (source),
+                                NULL);
+  return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_raitv_plugin_init,
+                     NULL,
+                     PLUGIN_ID);
+
+
+/* ================== Rai.tv GObject ================ */
+
+static GrlRaitvSource *
+grl_raitv_source_new (void)
+{
+  return g_object_new (GRL_TYPE_RAITV_SOURCE,
+                       "source-id", SOURCE_ID,
+                       "source-name", SOURCE_NAME,
+                       "source-desc", SOURCE_DESC,
+                       "supported-media", GRL_MEDIA_TYPE_VIDEO,
+                       NULL);
+}
+
+static void
+grl_raitv_source_dispose (GObject *object)
+{
+  G_OBJECT_CLASS (grl_raitv_source_parent_class)->dispose (object);
+}
+
+static void
+grl_raitv_source_finalize (GObject *object)
+{
+  GrlRaitvSource *source = GRL_RAITV_SOURCE (object);
+
+  if (source->priv->wc != NULL) {
+    g_object_unref (source->priv->wc);
+    source->priv->wc = NULL;
+  }
+
+  if (source->priv->raitv_search_mappings != NULL) {
+    g_object_unref (source->priv->raitv_search_mappings);
+    source->priv->raitv_search_mappings = NULL;
+  }
+
+  if (source->priv->raitv_browse_mappings != NULL) {
+    g_object_unref (source->priv->raitv_browse_mappings);
+    source->priv->raitv_browse_mappings = NULL;
+  }
+
+
+  G_OBJECT_CLASS (grl_raitv_source_parent_class)->finalize (object);
+}
+
+static void
+grl_raitv_source_class_init (GrlRaitvSourceClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass);
+
+  g_type_class_add_private (klass, sizeof (GrlRaitvSourcePrivate));
+
+  object_class->dispose = grl_raitv_source_dispose;
+  object_class->finalize = grl_raitv_source_finalize;
+
+  source_class->supported_keys = grl_raitv_source_supported_keys;
+  source_class->slow_keys = grl_raitv_source_slow_keys;
+
+  source_class->cancel = grl_raitv_source_cancel;
+  source_class->browse = grl_raitv_source_browse;
+  source_class->search = grl_raitv_source_search;
+  source_class->resolve = grl_raitv_source_resolve;
+
+}
+
+static RaitvAssoc *
+raitv_build_mapping (GrlKeyID grl_key, const gchar *exp)
+{
+  RaitvAssoc *assoc = g_new (RaitvAssoc, 1);
+
+  assoc->grl_key = grl_key;
+  assoc->exp     = exp;
+
+  return assoc;
+}
+
+static void
+grl_raitv_source_init (GrlRaitvSource *self)
+{
+  self->priv = RAITV_SOURCE_PRIVATE (self);
+
+  self->priv->wc = grl_net_wc_new ();
+  grl_net_wc_set_throttling (self->priv->wc, 1);
+
+  //Insert search mapping
+  self->priv->raitv_search_mappings = g_list_append (self->priv->raitv_search_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_ID, "HAS/C/@CID"));
+
+  self->priv->raitv_search_mappings = g_list_append (self->priv->raitv_search_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_PUBLICATION_DATE, "MT[ N='itemDate']/@V"));
+
+  self->priv->raitv_search_mappings = g_list_append (self->priv->raitv_search_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_TITLE, "MT[ N='title\']/@V"));
+
+  self->priv->raitv_search_mappings = g_list_append (self->priv->raitv_search_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_URL, "MT[ N='videourl']/@V"));
+
+  self->priv->raitv_search_mappings = g_list_append (self->priv->raitv_search_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_THUMBNAIL, "MT[ N='vod-image']/@V"));
+
+
+  //Insert browse mapping
+  self->priv->raitv_browse_mappings = g_list_append (self->priv->raitv_browse_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_ID, "localid"));
+  self->priv->raitv_browse_mappings = g_list_append (self->priv->raitv_browse_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_PUBLICATION_DATE, "datacreazione"));
+  self->priv->raitv_browse_mappings = g_list_append (self->priv->raitv_browse_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_TITLE, "titolo"));
+  self->priv->raitv_browse_mappings = g_list_append (self->priv->raitv_browse_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_URL, "h264"));
+  self->priv->raitv_browse_mappings = g_list_append (self->priv->raitv_browse_mappings,
+                                                     raitv_build_mapping(GRL_METADATA_KEY_THUMBNAIL, "pathImmagine"));
+
+}
+
+
+static void
+raitv_operation_free (RaitvOperation *op)
+{
+  if (op->cancellable)
+    g_object_unref (op->cancellable);
+  if (op->source)
+    g_object_unref (op->source);
+  g_slice_free (RaitvOperation, op);
+}
+
+
+/* ================== Callbacks ================ */
+
+static void
+proxy_call_search_grlnet_async_cb (GObject *source_object,
+                                   GAsyncResult *res,
+                                   gpointer user_data)
+{
+  RaitvOperation    *op = (RaitvOperation *) user_data;
+  xmlDocPtr           doc = NULL;
+  xmlXPathContextPtr  xpath = NULL;
+  xmlXPathObjectPtr   obj = NULL;
+  gint i, nb_items = 0;
+  gint length;
+
+  GRL_DEBUG ("Response id=%u", op->operation_id);
+
+  GError *wc_error = NULL;
+  GError *error = NULL;
+  gchar *content = NULL;
+
+  if (g_cancellable_is_cancelled (op->cancellable)) {
+    goto finalize;
+  }
+
+  if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
+                                  res,
+                                  &content,
+                                  (gsize *) &length,
+                                  &wc_error)) {
+    error = g_error_new (GRL_CORE_ERROR,
+                         GRL_CORE_ERROR_SEARCH_FAILED,
+                         "Failed to search Rait.tv: '%s'",
+                         wc_error->message);
+
+    op->callback (op->source,
+                  op->operation_id,
+                  NULL,
+                  0,
+                  op->user_data,
+                  error);
+
+   g_error_free (wc_error);
+    g_error_free (error);
+
+    return;
+  }
+
+  doc = xmlParseMemory (content, length);
+
+  if (!doc) {
+    GRL_DEBUG ("Doc failed");
+    goto finalize;
+  }
+
+  xpath = xmlXPathNewContext (doc);
+  if (!xpath) {
+    GRL_DEBUG ("Xpath failed");
+    goto finalize;
+  }
+  obj = xmlXPathEvalExpression ((xmlChar *) "/GSP/RES/R", xpath);
+  if (obj)
+    {
+      nb_items = xmlXPathNodeSetGetLength (obj->nodesetval);
+      xmlXPathFreeObject (obj);
+    }
+
+  gboolean g_bVideoNotFound = TRUE;
+
+  for (i = 0; i < nb_items; i++)
+    {
+      //Search only videos
+      gchar *str;
+      str = g_strdup_printf ("string(/GSP/RES/R[%i]/MT[ N='videourl']/@V)",
+                             i + 1);
+      obj = xmlXPathEvalExpression ((xmlChar *) str, xpath);
+      if (obj->stringval && obj->stringval[0] == '\0')
+        continue;
+      if(op->skip>0) {
+        op->skip--;
+        continue;
+      }
+
+      GrlRaitvSource *source = GRL_RAITV_SOURCE (op->source);
+      GList *mapping = source->priv->raitv_search_mappings;
+      GrlMedia *media = grl_media_video_new ();
+      g_bVideoNotFound = FALSE;
+      GRL_DEBUG ("Mappings count: %d",g_list_length(mapping));
+      while (mapping)
+        {
+          RaitvAssoc *assoc = (RaitvAssoc *) mapping->data;
+          str = g_strdup_printf ("string(/GSP/RES/R[%i]/%s)",
+                                 i + 1, assoc->exp);
+
+          GRL_DEBUG ("Xquery %s", str);
+          gchar *strvalue;
+
+          obj = xmlXPathEvalExpression ((xmlChar *) str, xpath);
+          if (obj)
+            {
+              if (obj->stringval && obj->stringval[0] != '\0')
+                {
+                  strvalue = 	g_strdup((gchar *) obj->stringval);
+                  //Sometimes GRL_METADATA_KEY_THUMBNAIL doesn't report complete url
+                  if(assoc->grl_key == GRL_METADATA_KEY_THUMBNAIL && !g_str_has_prefix(strvalue,"http://www.rai.tv";)) {
+                    strvalue = g_strdup_printf("http://www.rai.tv%s",obj->stringval);
+                  }
+
+                  GType _type;
+                  GRL_DEBUG ("\t%s -> %s", str, obj->stringval);
+                  _type = grl_metadata_key_get_type (assoc->grl_key);
+                  switch (_type)
+                    {
+                    case G_TYPE_STRING:
+                      grl_data_set_string (GRL_DATA (media),
+                                           assoc->grl_key,
+                                           strvalue);
+                      break;
+
+                    case G_TYPE_INT:
+                      grl_data_set_int (GRL_DATA (media),
+                                        assoc->grl_key,
+                                        (gint) atoi (strvalue));
+                      break;
+
+                    case G_TYPE_FLOAT:
+                      grl_data_set_float (GRL_DATA (media),
+                                          assoc->grl_key,
+                                          (gfloat) atof (strvalue));
+                      break;
+
+                    default:
+                      /* G_TYPE_DATE_TIME is not a constant, so this has to be
+                       * in "default:" */
+                      if (_type == G_TYPE_DATE_TIME) {
+                        int year,month,day;
+                        sscanf((const char*)obj->stringval, "%02d/%02d/%04d", &day, &month, &year);
+                        GDateTime *date = g_date_time_new_local (year, month, day, 0, 0, 0);
+                        GRL_DEBUG ("Setting %s to %s",
+                                   grl_metadata_key_get_name (assoc->grl_key),
+                                   g_date_time_format (date, "%F %H:%M:%S"));
+                        grl_data_set_boxed (GRL_DATA (media),
+                                            assoc->grl_key, date);
+                        if(date) g_date_time_unref (date);
+                      } else {
+                        GRL_DEBUG ("\tUnexpected data type: %s",
+                                   g_type_name (_type));
+                      }
+                      break;
+                    }
+                  g_free (strvalue);
+                }
+              xmlXPathFreeObject (obj);
+            }
+
+          g_free (str);
+
+          mapping = mapping->next;
+        }
+
+      op->callback (op->source,
+                    op->operation_id,
+                    media,
+                    --op->count,
+                    op->user_data,
+                    NULL);
+
+      if (op->count == 0)
+        break;
+    }
+
+ finalize:
+
+  if (xpath)
+    xmlXPathFreeContext (xpath);
+  if (doc)
+    xmlFreeDoc (doc);
+
+  /* Signal the last element if it was not already signaled */
+  if (nb_items == 0 || g_bVideoNotFound) {
+    op->callback (op->source,
+                  op->operation_id,
+                  NULL,
+                  0,
+                  op->user_data,
+                  NULL);
+    raitv_operation_free (op);
+  }
+  else {
+    //Continue the search
+    if(op->count>0) {
+		op->offset +=  nb_items;
+		g_raitv_videos_search(op);
+    }
+  }
+
+}
+
+
+
+static void
+proxy_call_browse_grlnet_async_cb (GObject *source_object,
+                                   GAsyncResult *res,
+                                   gpointer user_data)
+{
+  RaitvOperation    *op = (RaitvOperation *) user_data;
+  xmlDocPtr           doc = NULL;
+  xmlXPathContextPtr  xpath = NULL;
+  xmlXPathObjectPtr   obj = NULL;
+  gint i, nb_items = 0;
+  gint length;
+
+
+  GRL_DEBUG ("Response id=%u", op->operation_id);
+
+  GError *wc_error = NULL;
+  GError *error = NULL;
+  gchar *content = NULL;
+
+  if (g_cancellable_is_cancelled (op->cancellable)) {
+    goto finalize;
+  }
+
+
+  if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
+                                  res,
+                                  &content,
+                                  (gsize *) &length,
+                                  &wc_error)) {
+    error = g_error_new (GRL_CORE_ERROR,
+                         GRL_CORE_ERROR_SEARCH_FAILED,
+                         "Failed to browse Rait.tv: '%s'",
+                         wc_error->message);
+
+    op->callback (op->source,
+                  op->operation_id,
+                  NULL,
+                  0,
+                  op->user_data,
+                  error);
+
+    g_error_free (wc_error);
+    g_error_free (error);
+
+    return;
+  }
+
+  doc = xmlRecoverMemory (content, length);
+
+  if (!doc) {
+    GRL_DEBUG ("Doc failed");
+    goto finalize;
+  }
+
+  xpath = xmlXPathNewContext (doc);
+  if (!xpath) {
+    GRL_DEBUG ("Xpath failed");
+    goto finalize;
+  }
+
+  gchar* xquery=NULL;
+  switch (classify_media_id (op->container_id))
+    {
+    case RAITV_MEDIA_TYPE_POPULAR_THEME:
+      xquery = "/CLASSIFICAVISTI/content";
+      break;
+    case RAITV_MEDIA_TYPE_RECENT_THEME:
+      xquery = "/INFORMAZIONICONTENTS/content";
+      break;
+    default:
+      goto finalize;
+    }
+
+  obj = xmlXPathEvalExpression ((xmlChar *) xquery, xpath);
+  if (obj)
+    {
+      nb_items = xmlXPathNodeSetGetLength (obj->nodesetval);
+      xmlXPathFreeObject (obj);
+    }
+
+  if (nb_items < op->count)
+    op->count = nb_items;
+
+  op->category_info->count = nb_items;
+
+  for (i = 0; i < nb_items; i++)
+    {
+
+      //Skip
+      if(op->skip>0) {
+        op->skip--;
+        continue;
+      }
+
+      GrlRaitvSource *source = GRL_RAITV_SOURCE (op->source);
+      GList *mapping = source->priv->raitv_browse_mappings;
+      GrlMedia *media = grl_media_video_new ();
+
+      while (mapping)
+        {
+          gchar *str;
+          gchar *strvalue;
+
+          RaitvAssoc *assoc = (RaitvAssoc *) mapping->data;
+          str = g_strdup_printf ("string(%s[%i]/%s)",
+                                 xquery,i + 1, assoc->exp);
+
+
+          obj = xmlXPathEvalExpression ((xmlChar *) str, xpath);
+          if (obj)
+            {
+              if (obj->stringval && obj->stringval[0] != '\0')
+                {
+                  strvalue = 	g_strdup((gchar *) obj->stringval);
+                  //Sometimes GRL_METADATA_KEY_THUMBNAIL doesn't report complete url
+                  if(assoc->grl_key == GRL_METADATA_KEY_THUMBNAIL && !g_str_has_prefix(strvalue,"http://www.rai.tv/";)) {
+                    strvalue = g_strdup_printf("http://www.rai.tv%s",obj->stringval);
+                  }
+
+                  GType _type;
+                  _type = grl_metadata_key_get_type (assoc->grl_key);
+                  switch (_type)
+                    {
+                    case G_TYPE_STRING:
+                      grl_data_set_string (GRL_DATA (media),
+                                           assoc->grl_key,
+                                           strvalue);
+                      break;
+
+                    case G_TYPE_INT:
+                      grl_data_set_int (GRL_DATA (media),
+                                        assoc->grl_key,
+                                        (gint) atoi (strvalue));
+                      break;
+
+                    case G_TYPE_FLOAT:
+                      grl_data_set_float (GRL_DATA (media),
+                                          assoc->grl_key,
+                                          (gfloat) atof (strvalue));
+                      break;
+
+                    default:
+                      /* G_TYPE_DATE_TIME is not a constant, so this has to be
+                       * in "default:" */
+                      if (_type == G_TYPE_DATE_TIME) {
+                        int year,month,day,hour,minute,seconds;
+                        sscanf((const char*)obj->stringval, "%02d/%02d/%04d %02d:%02d:%02d",
+                               &day, &month, &year, &hour,&minute, &seconds);
+                        GDateTime *date = g_date_time_new_local (year, month, day, hour,minute, seconds);
+                        grl_data_set_boxed (GRL_DATA (media),
+                                            assoc->grl_key, date);
+                        if(date) g_date_time_unref (date);
+                      } else {
+                        GRL_DEBUG ("\tUnexpected data type: %s",
+                                   g_type_name (_type));
+                      }
+                      break;
+                    }
+                  g_free (strvalue);
+                }
+              xmlXPathFreeObject (obj);
+            }
+
+          g_free (str);
+
+          mapping = mapping->next;
+        }
+
+      op->callback (op->source,
+                    op->operation_id,
+                    media,
+                    --op->count,
+                    op->user_data,
+                    NULL);
+
+      if (op->count == 0) {
+        break;
+      }
+    }
+
+ finalize:
+  //g_free (body);
+
+  if (xpath)
+    xmlXPathFreeContext (xpath);
+  if (doc)
+    xmlFreeDoc (doc);
+
+  /* Signal the last element if it was not already signaled */
+  if (nb_items == 0) {
+    op->callback (op->source,
+                  op->operation_id,
+                  NULL,
+                  0,
+                  op->user_data,
+                  NULL);
+    raitv_operation_free (op);
+  }
+  else {
+    //Continue the search
+    if(op->count>0) {
+      //Skip the ones already read
+      op->skip +=  nb_items;
+      op->offset +=  nb_items;
+      switch (classify_media_id (op->container_id))
+		  {
+        case RAITV_MEDIA_TYPE_POPULAR_THEME:
+          produce_from_popular_theme(op);
+          break;
+        case RAITV_MEDIA_TYPE_RECENT_THEME:
+          produce_from_recent_theme(op);
+          break;
+        default:
+          g_assert_not_reached ();
+          break;
+		  }
+
+    }
+  }
+
+}
+
+
+
+static void
+proxy_call_resolve_grlnet_async_cb (GObject *source_object,
+                                    GAsyncResult *res,
+                                    gpointer user_data)
+{
+  RaitvOperation    *op = (RaitvOperation *) user_data;
+  xmlDocPtr           doc = NULL;
+  xmlXPathContextPtr  xpath = NULL;
+  xmlXPathObjectPtr   obj = NULL;
+
+  GRL_DEBUG ("Response id=%u", op->operation_id);
+
+  GError *wc_error = NULL;
+  GError *error = NULL;
+  gchar *content = NULL;
+  gint length;
+
+  if (g_cancellable_is_cancelled (op->cancellable)) {
+    goto finalize;
+  }
+
+
+  if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
+                                  res,
+                                  &content,
+                                  (gsize *) &length,
+                                  &wc_error)) {
+    error = g_error_new (GRL_CORE_ERROR,
+                         GRL_CORE_ERROR_SEARCH_FAILED,
+                         "Failed to resolve Rait.tv: '%s'",
+                         wc_error->message);
+
+    op->resolveCb (op->source,
+                   op->operation_id,
+                   op->media,
+                   op->user_data,
+                   error);
+
+    g_error_free (wc_error);
+    g_error_free (error);
+
+    return;
+  }
+
+  doc = xmlRecoverMemory (content, length);
+
+  if (!doc) {
+    GRL_DEBUG ("Doc failed");
+    goto finalize;
+  }
+
+  xpath = xmlXPathNewContext (doc);
+  if (!xpath) {
+    GRL_DEBUG ("Xpath failed");
+    goto finalize;
+  }
+
+  gchar* xquery="/html/head/meta[ name='videourl']";
+
+  obj = xmlXPathEvalExpression ((xmlChar *) xquery, xpath);
+  if(obj!=NULL) {
+    xmlNodePtr curNode = NULL;
+    xmlChar	*szValue = NULL;
+    xmlNodeSetPtr nodeset = obj->nodesetval;
+    for (int i = 0; i < nodeset->nodeNr; i++)
+      {
+        curNode = nodeset->nodeTab[i];
+        if(curNode != NULL)
+          {
+            szValue = xmlGetProp(curNode,BAD_CAST "content");
+            if (szValue != NULL)
+              {
+                grl_media_set_url(op->media,(gchar*)szValue);
+                xmlFree(szValue);
+              }
+          }
+      }
+  }
+
+  op->resolveCb (op->source,
+                 op->operation_id,
+                 op->media,
+                 op->user_data,
+                 NULL);
+
+ finalize:
+  if (xpath)
+    xmlXPathFreeContext (xpath);
+
+  if (doc)
+    xmlFreeDoc (doc);
+
+}
+
+
+
+static gint
+get_theme_index_from_id (const gchar *category_id)
+{
+  gint i;
+
+  for (i=0; i<root_dir[ROOT_DIR_POPULARS_INDEX].count; i++) {
+    if (g_strrstr (category_id, themes_dir[i].id)) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+static gboolean
+is_popular_container (const gchar *container_id)
+{
+  return g_str_has_prefix (container_id, RAITV_POPULARS_THEME_ID "/");
+}
+
+static gboolean
+is_recent_container (const gchar *container_id)
+{
+  return g_str_has_prefix (container_id, RAITV_RECENTS_THEME_ID "/");
+}
+
+static RaitvMediaType
+classify_media_id (const gchar *media_id)
+{
+  if (!media_id) {
+    return RAITV_MEDIA_TYPE_ROOT;
+  } else if (!strcmp (media_id, RAITV_POPULARS_ID)) {
+    return RAITV_MEDIA_TYPE_POPULARS;
+  } else if (!strcmp (media_id, RAITV_RECENTS_ID)) {
+    return RAITV_MEDIA_TYPE_RECENTS;
+  } else if (is_popular_container (media_id)) {
+    return RAITV_MEDIA_TYPE_POPULAR_THEME;
+  } else if (is_recent_container (media_id)) {
+    return RAITV_MEDIA_TYPE_RECENT_THEME;
+  } else {
+    return RAITV_MEDIA_TYPE_VIDEO;
+  }
+}
+
+
+static GrlMedia *
+produce_container_from_directory (GrlMedia *media,
+                                  CategoryInfo *dir,
+                                  guint index,
+                                  RaitvMediaType type)
+{
+  GrlMedia *content;
+  gchar* mediaid=NULL;
+
+  if (!media) {
+    content = grl_media_box_new ();
+  } else {
+    content = media;
+  }
+
+  if (!dir) {
+    grl_media_set_id (content, NULL);
+    grl_media_set_title (content, RAITV_ROOT_NAME);
+  } else {
+
+    switch(type)
+      {
+      case RAITV_MEDIA_TYPE_ROOT :
+      case RAITV_MEDIA_TYPE_POPULARS :
+      case RAITV_MEDIA_TYPE_RECENTS :
+        mediaid = g_strdup_printf("%s",dir[index].id);
+        break;
+      case RAITV_MEDIA_TYPE_POPULAR_THEME :
+        mediaid = g_strdup_printf("%s/%s", RAITV_POPULARS_THEME_ID, dir[index].id);
+        break;
+      case RAITV_MEDIA_TYPE_RECENT_THEME :
+        mediaid = g_strdup_printf("%s/%s", RAITV_RECENTS_THEME_ID, dir[index].id);
+        break;
+      default: break;
+      }
+
+    GRL_DEBUG ("MediaId=%s, Type:%d, Titolo:%s",mediaid, type, dir[index].name);
+
+    grl_media_set_id (content, mediaid);
+    grl_media_set_title (content, dir[index].name);
+    g_free(mediaid);
+  }
+
+  return content;
+}
+
+static void
+produce_from_directory (CategoryInfo *dir, gint dir_size, RaitvOperation *os,
+                        RaitvMediaType type)
+{
+  guint index, remaining;
+
+  GRL_DEBUG ("Produce_from_directory. Size=%d",dir_size);
+
+  if (os->skip >= dir_size) {
+    /* No results */
+    os->callback (os->source,
+                  os->operation_id,
+                  NULL,
+                  0,
+                  os->user_data,
+                  NULL);
+  } else {
+    index = os->skip;
+    remaining = MIN (dir_size - os->skip, os->count);
+
+    do {
+      GrlMedia *content =
+        produce_container_from_directory (NULL, dir, index,type );
+
+      remaining--;
+      index++;
+
+      os->callback (os->source,
+                    os->operation_id,
+                    content,
+                    remaining,
+                    os->user_data,
+                    NULL);
+
+    } while (remaining > 0);
+  }
+}
+
+static void
+produce_from_popular_theme (RaitvOperation *op)
+{
+  gint category_index;
+  gchar	*start = NULL;
+  gchar	*url = NULL;
+
+  GrlRaitvSource *source = GRL_RAITV_SOURCE (op->source);
+  start = g_strdup_printf ("%u", op->offset+op->length);
+
+  category_index = get_theme_index_from_id (op->container_id);
+  GRL_DEBUG ("produce_from_popular_theme (container_id=%s, category_index=%d",op->container_id,category_index);
+
+  op->category_info = &themes_dir[category_index];
+  url = g_strdup_printf (RAITV_VIDEO_POPULAR, start, op->category_info->tags, op->category_info->excludeTags);
+
+  GRL_DEBUG ("Starting browse request for popular theme (%s)", url);
+  grl_net_wc_request_async (source->priv->wc,
+                            url,
+                            op->cancellable,
+                            proxy_call_browse_grlnet_async_cb,
+                            op);
+
+  g_free (url);
+}
+
+
+
+static void
+produce_from_recent_theme (RaitvOperation *op)
+{
+  gint category_index;
+  gchar	*start = NULL;
+  gchar	*url = NULL;
+
+  GrlRaitvSource *source = GRL_RAITV_SOURCE (op->source);
+
+  category_index = get_theme_index_from_id (op->container_id);
+  GRL_DEBUG ("produce_from_recent_theme (container_id=%s, category_index=%d",op->container_id,category_index);
+
+  start = g_strdup_printf ("%u", op->offset+op->length);
+
+  op->category_info = &themes_dir[category_index];
+  url = g_strdup_printf (RAITV_VIDEO_RECENT, start, op->category_info->tags, op->category_info->excludeTags);
+
+  GRL_DEBUG ("Starting browse request for recent theme (%s)", url);
+  grl_net_wc_request_async (source->priv->wc,
+                            url,
+                            op->cancellable,
+                            proxy_call_browse_grlnet_async_cb,
+                            op);
+
+  g_free (url);
+}
+
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_raitv_source_supported_keys (GrlSource *source)
+{
+  static GList *keys = NULL;
+  if (!keys) {
+    keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+                                      GRL_METADATA_KEY_PUBLICATION_DATE,
+                                      GRL_METADATA_KEY_TITLE,
+                                      GRL_METADATA_KEY_URL,
+                                      GRL_METADATA_KEY_THUMBNAIL,
+                                      NULL);
+  }
+  return keys;
+}
+
+static const GList *
+grl_raitv_source_slow_keys (GrlSource *source)
+{
+  static GList *keys = NULL;
+  if (!keys) {
+    keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL,
+                                      NULL);
+  }
+  return keys;
+}
+
+
+static void
+grl_raitv_source_browse (GrlSource *source,
+                         GrlSourceBrowseSpec *bs)
+{
+  RaitvOperation *op = g_slice_new0 (RaitvOperation);
+
+  const gchar *container_id;
+  GRL_DEBUG ("%s: %s", __FUNCTION__, grl_media_get_id (bs->container));
+  container_id = grl_media_get_id (bs->container);
+
+  op->source       = g_object_ref (source);
+  op->cancellable  = g_cancellable_new ();
+  op->length        = grl_operation_options_get_count (bs->options);
+  op->operation_id = bs->operation_id;
+  op->container_id = container_id;
+  op->callback     = bs->callback;
+  op->user_data    = bs->user_data;
+  op->skip	   = grl_operation_options_get_skip (bs->options);
+  op->count	   = op->length;
+  op->offset       = 0;
+
+  grl_operation_set_data (bs->operation_id, op);
+
+  RaitvMediaType type = classify_media_id (container_id);
+  switch (type)
+    {
+    case RAITV_MEDIA_TYPE_ROOT:
+      produce_from_directory (root_dir, root_dir_size, op, type);
+      break;
+    case RAITV_MEDIA_TYPE_POPULARS:
+      produce_from_directory (themes_dir,
+                              root_dir[ROOT_DIR_POPULARS_INDEX].count, op, RAITV_MEDIA_TYPE_POPULAR_THEME);
+      break;
+    case RAITV_MEDIA_TYPE_RECENTS:
+      produce_from_directory (themes_dir,
+                              root_dir[ROOT_DIR_RECENTS_INDEX].count, op, RAITV_MEDIA_TYPE_RECENT_THEME);
+      break;
+    case RAITV_MEDIA_TYPE_POPULAR_THEME:
+      produce_from_popular_theme (op);
+      break;
+    case RAITV_MEDIA_TYPE_RECENT_THEME:
+      produce_from_recent_theme (op);
+      break;
+    case RAITV_MEDIA_TYPE_VIDEO:
+    default:
+      g_assert_not_reached ();
+      break;
+    }
+
+}
+
+static void
+grl_raitv_source_search (GrlSource *source,
+                         GrlSourceSearchSpec *ss)
+{
+  RaitvOperation *op = g_slice_new0 (RaitvOperation);
+
+  op->source       = g_object_ref (source);
+  op->cancellable  = g_cancellable_new ();
+  op->length        = grl_operation_options_get_count (ss->options);
+  op->operation_id = ss->operation_id;
+  op->callback     = ss->callback;
+  op->user_data    = ss->user_data;
+  op->skip	   = grl_operation_options_get_skip (ss->options);
+  op->count	   = op->length;
+  op->offset       = 0;
+  op->text	   = ss->text;
+
+  grl_operation_set_data (ss->operation_id, op);
+
+  g_raitv_videos_search(op);
+}
+
+static void
+g_raitv_videos_search(RaitvOperation *op) {
+
+  gchar *start;
+  gchar *url = NULL;
+
+  GrlRaitvSource *source = GRL_RAITV_SOURCE (op->source);
+
+  start = g_strdup_printf ("%u", op->offset);
+
+  url = g_strdup_printf (RAITV_VIDEO_SEARCH, op->text, start);
+
+  GRL_DEBUG ("Starting search request (%s)", url);
+  grl_net_wc_request_async (source->priv->wc,
+                            url,
+                            op->cancellable,
+                            proxy_call_search_grlnet_async_cb,
+                            op);
+  g_free (start);
+  g_free (url);
+
+}
+
+
+static void
+grl_raitv_source_resolve (GrlSource *source,
+                          GrlSourceResolveSpec *rs)
+{
+  gchar *urltarget;
+
+  GRL_DEBUG ("Starting resolve source: url=%s",grl_media_get_url (rs->media));
+
+  if (!GRL_IS_MEDIA_VIDEO (rs->media))
+    return;
+
+  if ( grl_media_get_url (rs->media) != NULL) {
+    rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL);
+    return;
+  }
+
+  RaitvOperation *op = g_slice_new0 (RaitvOperation);
+  op->source       = g_object_ref (source);
+  op->cancellable  = g_cancellable_new ();
+  op->operation_id = rs->operation_id;
+  op->resolveCb     = rs->callback;
+  op->user_data    = rs->user_data;
+  op->media	   = rs->media;
+
+  grl_operation_set_data (rs->operation_id, op);
+
+  urltarget = g_strdup_printf("%s/%s.html","http://www.rai.tv/dl/RaiTV/programmi/media",grl_media_get_id(rs->media));
+
+  GRL_DEBUG ("Opening '%s'", urltarget);
+
+  GrlRaitvSource *self = GRL_RAITV_SOURCE (op->source);
+
+  grl_net_wc_request_async (self->priv->wc,
+                            urltarget,
+                            op->cancellable,
+                            proxy_call_resolve_grlnet_async_cb,
+                            op);
+
+  g_free(urltarget);
+}
+
+
+static void
+grl_raitv_source_cancel (GrlSource *source, guint operation_id)
+{
+  RaitvOperation *op = grl_operation_get_data (operation_id);
+
+  GRL_WARNING ("Cancelling id=%u", operation_id);
+
+  if (!op)
+    {
+      GRL_WARNING ("\tNo such operation id=%u", operation_id);
+    }
+
+  if (op->cancellable) {
+    g_cancellable_cancel (op->cancellable);
+  }
+}
diff --git a/src/raitv/grl-raitv.h b/src/raitv/grl-raitv.h
new file mode 100644
index 0000000..1b8f4ee
--- /dev/null
+++ b/src/raitv/grl-raitv.h
@@ -0,0 +1,76 @@
+/*
+ * Authors: Marco Piazza <mpiazza gmail com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public License
+ * as published by the Free Software Foundation; version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that 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 library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA
+ *
+ */
+
+#ifndef _GRL_RAITV_H
+#define _GRL_RAITV_H
+
+#include <glib-object.h>
+#include <grilo.h>
+
+G_BEGIN_DECLS
+
+#define GRL_TYPE_RAITV_SOURCE grl_raitv_source_get_type()
+
+#define GRL_RAITV_SOURCE(obj)                         \
+  (G_TYPE_CHECK_INSTANCE_CAST ((obj),                 \
+                               GRL_TYPE_RAITV_SOURCE, \
+                               GrlRaitvSource))
+
+#define GRL_RAITV_SOURCE_CLASS(klass)              \
+  (G_TYPE_CHECK_CLASS_CAST ((klass),               \
+                            GRL_TYPE_RAITV_SOURCE, \
+                            GrlRaitvSourceClass))
+
+#define GRL_IS_RAITV_SOURCE(obj)                         \
+  (G_TYPE_CHECK_INSTANCE_TYPE ((obj),                    \
+                               GRL_TYPE_RAITV_SOURCE))
+
+
+#define GRL_IS_RAITV_SOURCE_CLASS(klass)              \
+  (G_TYPE_CHECK_CLASS_TYPE ((klass),                  \
+                            GRL_TYPE_RAITV_SOURCE))
+
+#define GRL_RAITV_SOURCE_GET_CLASS(obj)               \
+  (G_TYPE_INSTANCE_GET_CLASS ((obj),                  \
+                              GRL_TYPE_RAITV_SOURCE,  \
+                              GrlRaitvSourceClass))
+
+typedef struct _GrlRaitvSource        GrlRaitvSource;
+typedef struct _GrlRaitvSourceClass   GrlRaitvSourceClass;
+typedef struct _GrlRaitvSourcePrivate GrlRaitvSourcePrivate;
+
+struct _GrlRaitvSource
+{
+  GrlSource parent;
+
+  /*< private >*/
+  GrlRaitvSourcePrivate *priv;
+};
+
+struct _GrlRaitvSourceClass
+{
+  GrlSourceClass parent_class;
+};
+
+GType grl_raitv_source_get_type (void) G_GNUC_CONST;
+
+G_END_DECLS
+
+#endif /* _GRL_RAITV_H */
diff --git a/src/raitv/grl-raitv.xml b/src/raitv/grl-raitv.xml
new file mode 100644
index 0000000..563f5da
--- /dev/null
+++ b/src/raitv/grl-raitv.xml
@@ -0,0 +1,10 @@
+<plugin>
+  <info>
+    <name>Rai.tv</name>
+    <module>libgrlraitv</module>
+    <description>A plugin for searching multimedia content using Rai.tv</description>
+    <author>Marco Piazza</author>
+    <license>LGPL</license>
+    <site>http://live.gnome.org/Grilo</site>
+  </info>
+</plugin>



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