[PATCH 1/3] split src/ content into src/media and src/metadata
- From: Guillaume Emont <gemont igalia com>
- To: grilo-list gnome org
- Subject: [PATCH 1/3] split src/ content into src/media and src/metadata
- Date: Thu, 24 Feb 2011 20:06:18 +0100
---
src/apple-trailers/Makefile.am | 38 -
src/apple-trailers/grl-apple-trailers.c | 611 --------
src/apple-trailers/grl-apple-trailers.h | 78 -
src/apple-trailers/grl-apple-trailers.xml | 9 -
src/bookmarks/Makefile.am | 34 -
src/bookmarks/grl-bookmarks.c | 888 -----------
src/bookmarks/grl-bookmarks.h | 75 -
src/bookmarks/grl-bookmarks.xml | 9 -
src/fake-metadata/Makefile.am | 32 -
src/fake-metadata/grl-fake-metadata.c | 231 ---
src/fake-metadata/grl-fake-metadata.h | 72 -
src/fake-metadata/grl-fake-metadata.xml | 9 -
src/filesystem/Makefile.am | 34 -
src/filesystem/TODO | 2 -
src/filesystem/grl-filesystem.c | 1343 ----------------
src/filesystem/grl-filesystem.h | 81 -
src/filesystem/grl-filesystem.xml | 9 -
src/flickr/Makefile.am | 40 -
src/flickr/gflickr.c | 1196 --------------
src/flickr/gflickr.h | 155 --
src/flickr/grl-flickr.c | 755 ---------
src/flickr/grl-flickr.h | 80 -
src/flickr/grl-flickr.xml | 9 -
src/gravatar/Makefile.am | 32 -
src/gravatar/grl-gravatar.c | 301 ----
src/gravatar/grl-gravatar.h | 74 -
src/gravatar/grl-gravatar.xml | 9 -
src/jamendo/Makefile.am | 38 -
src/jamendo/TODO | 30 -
src/jamendo/grl-jamendo.c | 1365 ----------------
src/jamendo/grl-jamendo.h | 78 -
src/jamendo/grl-jamendo.xml | 9 -
src/lastfm-albumart/Makefile.am | 36 -
src/lastfm-albumart/grl-lastfm-albumart.c | 316 ----
src/lastfm-albumart/grl-lastfm-albumart.h | 74 -
src/lastfm-albumart/grl-lastfm-albumart.xml | 9 -
src/local-metadata/Makefile.am | 34 -
src/local-metadata/grl-local-metadata.c | 262 ----
src/local-metadata/grl-local-metadata.h | 72 -
src/local-metadata/grl-local-metadata.xml | 9 -
src/media/apple-trailers/Makefile.am | 38 +
src/media/apple-trailers/grl-apple-trailers.c | 611 ++++++++
src/media/apple-trailers/grl-apple-trailers.h | 78 +
src/media/apple-trailers/grl-apple-trailers.xml | 9 +
src/media/bookmarks/Makefile.am | 34 +
src/media/bookmarks/grl-bookmarks.c | 888 +++++++++++
src/media/bookmarks/grl-bookmarks.h | 75 +
src/media/bookmarks/grl-bookmarks.xml | 9 +
src/media/filesystem/Makefile.am | 34 +
src/media/filesystem/TODO | 2 +
src/media/filesystem/grl-filesystem.c | 1343 ++++++++++++++++
src/media/filesystem/grl-filesystem.h | 81 +
src/media/filesystem/grl-filesystem.xml | 9 +
src/media/flickr/Makefile.am | 40 +
src/media/flickr/gflickr.c | 1196 ++++++++++++++
src/media/flickr/gflickr.h | 155 ++
src/media/flickr/grl-flickr.c | 755 +++++++++
src/media/flickr/grl-flickr.h | 80 +
src/media/flickr/grl-flickr.xml | 9 +
src/media/jamendo/Makefile.am | 38 +
src/media/jamendo/TODO | 30 +
src/media/jamendo/grl-jamendo.c | 1365 ++++++++++++++++
src/media/jamendo/grl-jamendo.h | 78 +
src/media/jamendo/grl-jamendo.xml | 9 +
src/media/podcasts/Makefile.am | 38 +
src/media/podcasts/TODO | 5 +
src/media/podcasts/grl-podcasts.c | 1636 ++++++++++++++++++++
src/media/podcasts/grl-podcasts.h | 75 +
src/media/podcasts/grl-podcasts.xml | 9 +
src/media/shoutcast/Makefile.am | 37 +
src/media/shoutcast/grl-shoutcast.c | 727 +++++++++
src/media/shoutcast/grl-shoutcast.h | 74 +
src/media/shoutcast/grl-shoutcast.xml | 9 +
src/media/tracker/Makefile.am | 36 +
src/media/tracker/grl-tracker.c | 1511 ++++++++++++++++++
src/media/tracker/grl-tracker.h | 78 +
src/media/tracker/grl-tracker.xml | 9 +
src/media/upnp/Makefile.am | 46 +
src/media/upnp/grl-upnp.c | 1356 ++++++++++++++++
src/media/upnp/grl-upnp.h | 75 +
src/media/upnp/grl-upnp.xml | 9 +
src/media/vimeo/Makefile.am | 42 +
src/media/vimeo/grl-vimeo.c | 413 +++++
src/media/vimeo/grl-vimeo.h | 77 +
src/media/vimeo/grl-vimeo.xml | 9 +
src/media/vimeo/gvimeo.c | 517 ++++++
src/media/vimeo/gvimeo.h | 112 ++
src/media/youtube/Makefile.am | 40 +
src/media/youtube/TODO | 6 +
src/media/youtube/grl-youtube.c | 1630 +++++++++++++++++++
src/media/youtube/grl-youtube.h | 74 +
src/media/youtube/grl-youtube.xml | 9 +
src/metadata-store/Makefile.am | 34 -
src/metadata-store/grl-metadata-store.c | 650 --------
src/metadata-store/grl-metadata-store.h | 75 -
src/metadata-store/grl-metadata-store.xml | 9 -
src/metadata/fake-metadata/Makefile.am | 32 +
src/metadata/fake-metadata/grl-fake-metadata.c | 231 +++
src/metadata/fake-metadata/grl-fake-metadata.h | 72 +
src/metadata/fake-metadata/grl-fake-metadata.xml | 9 +
src/metadata/gravatar/Makefile.am | 32 +
src/metadata/gravatar/grl-gravatar.c | 301 ++++
src/metadata/gravatar/grl-gravatar.h | 74 +
src/metadata/gravatar/grl-gravatar.xml | 9 +
src/metadata/lastfm-albumart/Makefile.am | 36 +
src/metadata/lastfm-albumart/grl-lastfm-albumart.c | 316 ++++
src/metadata/lastfm-albumart/grl-lastfm-albumart.h | 74 +
.../lastfm-albumart/grl-lastfm-albumart.xml | 9 +
src/metadata/local-metadata/Makefile.am | 34 +
src/metadata/local-metadata/grl-local-metadata.c | 262 ++++
src/metadata/local-metadata/grl-local-metadata.h | 72 +
src/metadata/local-metadata/grl-local-metadata.xml | 9 +
src/metadata/metadata-store/Makefile.am | 34 +
src/metadata/metadata-store/grl-metadata-store.c | 650 ++++++++
src/metadata/metadata-store/grl-metadata-store.h | 75 +
src/metadata/metadata-store/grl-metadata-store.xml | 9 +
src/podcasts/Makefile.am | 38 -
src/podcasts/TODO | 5 -
src/podcasts/grl-podcasts.c | 1636 --------------------
src/podcasts/grl-podcasts.h | 75 -
src/podcasts/grl-podcasts.xml | 9 -
src/shoutcast/Makefile.am | 37 -
src/shoutcast/grl-shoutcast.c | 727 ---------
src/shoutcast/grl-shoutcast.h | 74 -
src/shoutcast/grl-shoutcast.xml | 9 -
src/tracker/Makefile.am | 36 -
src/tracker/grl-tracker.c | 1511 ------------------
src/tracker/grl-tracker.h | 78 -
src/tracker/grl-tracker.xml | 9 -
src/upnp/Makefile.am | 46 -
src/upnp/grl-upnp.c | 1356 ----------------
src/upnp/grl-upnp.h | 75 -
src/upnp/grl-upnp.xml | 9 -
src/vimeo/Makefile.am | 42 -
src/vimeo/grl-vimeo.c | 413 -----
src/vimeo/grl-vimeo.h | 77 -
src/vimeo/grl-vimeo.xml | 9 -
src/vimeo/gvimeo.c | 517 ------
src/vimeo/gvimeo.h | 112 --
src/youtube/Makefile.am | 40 -
src/youtube/TODO | 6 -
src/youtube/grl-youtube.c | 1630 -------------------
src/youtube/grl-youtube.h | 74 -
src/youtube/grl-youtube.xml | 9 -
144 files changed, 17965 insertions(+), 17965 deletions(-)
delete mode 100644 src/apple-trailers/Makefile.am
delete mode 100644 src/apple-trailers/grl-apple-trailers.c
delete mode 100644 src/apple-trailers/grl-apple-trailers.h
delete mode 100644 src/apple-trailers/grl-apple-trailers.xml
delete mode 100644 src/bookmarks/Makefile.am
delete mode 100644 src/bookmarks/grl-bookmarks.c
delete mode 100644 src/bookmarks/grl-bookmarks.h
delete mode 100644 src/bookmarks/grl-bookmarks.xml
delete mode 100644 src/fake-metadata/Makefile.am
delete mode 100644 src/fake-metadata/grl-fake-metadata.c
delete mode 100644 src/fake-metadata/grl-fake-metadata.h
delete mode 100644 src/fake-metadata/grl-fake-metadata.xml
delete mode 100644 src/filesystem/Makefile.am
delete mode 100644 src/filesystem/TODO
delete mode 100644 src/filesystem/grl-filesystem.c
delete mode 100644 src/filesystem/grl-filesystem.h
delete mode 100644 src/filesystem/grl-filesystem.xml
delete mode 100644 src/flickr/Makefile.am
delete mode 100644 src/flickr/gflickr.c
delete mode 100644 src/flickr/gflickr.h
delete mode 100644 src/flickr/grl-flickr.c
delete mode 100644 src/flickr/grl-flickr.h
delete mode 100644 src/flickr/grl-flickr.xml
delete mode 100644 src/gravatar/Makefile.am
delete mode 100644 src/gravatar/grl-gravatar.c
delete mode 100644 src/gravatar/grl-gravatar.h
delete mode 100644 src/gravatar/grl-gravatar.xml
delete mode 100644 src/jamendo/Makefile.am
delete mode 100644 src/jamendo/TODO
delete mode 100644 src/jamendo/grl-jamendo.c
delete mode 100644 src/jamendo/grl-jamendo.h
delete mode 100644 src/jamendo/grl-jamendo.xml
delete mode 100644 src/lastfm-albumart/Makefile.am
delete mode 100644 src/lastfm-albumart/grl-lastfm-albumart.c
delete mode 100644 src/lastfm-albumart/grl-lastfm-albumart.h
delete mode 100644 src/lastfm-albumart/grl-lastfm-albumart.xml
delete mode 100644 src/local-metadata/Makefile.am
delete mode 100644 src/local-metadata/grl-local-metadata.c
delete mode 100644 src/local-metadata/grl-local-metadata.h
delete mode 100644 src/local-metadata/grl-local-metadata.xml
create mode 100644 src/media/apple-trailers/Makefile.am
create mode 100644 src/media/apple-trailers/grl-apple-trailers.c
create mode 100644 src/media/apple-trailers/grl-apple-trailers.h
create mode 100644 src/media/apple-trailers/grl-apple-trailers.xml
create mode 100644 src/media/bookmarks/Makefile.am
create mode 100644 src/media/bookmarks/grl-bookmarks.c
create mode 100644 src/media/bookmarks/grl-bookmarks.h
create mode 100644 src/media/bookmarks/grl-bookmarks.xml
create mode 100644 src/media/filesystem/Makefile.am
create mode 100644 src/media/filesystem/TODO
create mode 100644 src/media/filesystem/grl-filesystem.c
create mode 100644 src/media/filesystem/grl-filesystem.h
create mode 100644 src/media/filesystem/grl-filesystem.xml
create mode 100644 src/media/flickr/Makefile.am
create mode 100644 src/media/flickr/gflickr.c
create mode 100644 src/media/flickr/gflickr.h
create mode 100644 src/media/flickr/grl-flickr.c
create mode 100644 src/media/flickr/grl-flickr.h
create mode 100644 src/media/flickr/grl-flickr.xml
create mode 100644 src/media/jamendo/Makefile.am
create mode 100644 src/media/jamendo/TODO
create mode 100644 src/media/jamendo/grl-jamendo.c
create mode 100644 src/media/jamendo/grl-jamendo.h
create mode 100644 src/media/jamendo/grl-jamendo.xml
create mode 100644 src/media/podcasts/Makefile.am
create mode 100644 src/media/podcasts/TODO
create mode 100644 src/media/podcasts/grl-podcasts.c
create mode 100644 src/media/podcasts/grl-podcasts.h
create mode 100644 src/media/podcasts/grl-podcasts.xml
create mode 100644 src/media/shoutcast/Makefile.am
create mode 100644 src/media/shoutcast/grl-shoutcast.c
create mode 100644 src/media/shoutcast/grl-shoutcast.h
create mode 100644 src/media/shoutcast/grl-shoutcast.xml
create mode 100644 src/media/tracker/Makefile.am
create mode 100644 src/media/tracker/grl-tracker.c
create mode 100644 src/media/tracker/grl-tracker.h
create mode 100644 src/media/tracker/grl-tracker.xml
create mode 100644 src/media/upnp/Makefile.am
create mode 100644 src/media/upnp/grl-upnp.c
create mode 100644 src/media/upnp/grl-upnp.h
create mode 100644 src/media/upnp/grl-upnp.xml
create mode 100644 src/media/vimeo/Makefile.am
create mode 100644 src/media/vimeo/grl-vimeo.c
create mode 100644 src/media/vimeo/grl-vimeo.h
create mode 100644 src/media/vimeo/grl-vimeo.xml
create mode 100644 src/media/vimeo/gvimeo.c
create mode 100644 src/media/vimeo/gvimeo.h
create mode 100644 src/media/youtube/Makefile.am
create mode 100644 src/media/youtube/TODO
create mode 100644 src/media/youtube/grl-youtube.c
create mode 100644 src/media/youtube/grl-youtube.h
create mode 100644 src/media/youtube/grl-youtube.xml
delete mode 100644 src/metadata-store/Makefile.am
delete mode 100644 src/metadata-store/grl-metadata-store.c
delete mode 100644 src/metadata-store/grl-metadata-store.h
delete mode 100644 src/metadata-store/grl-metadata-store.xml
create mode 100644 src/metadata/fake-metadata/Makefile.am
create mode 100644 src/metadata/fake-metadata/grl-fake-metadata.c
create mode 100644 src/metadata/fake-metadata/grl-fake-metadata.h
create mode 100644 src/metadata/fake-metadata/grl-fake-metadata.xml
create mode 100644 src/metadata/gravatar/Makefile.am
create mode 100644 src/metadata/gravatar/grl-gravatar.c
create mode 100644 src/metadata/gravatar/grl-gravatar.h
create mode 100644 src/metadata/gravatar/grl-gravatar.xml
create mode 100644 src/metadata/lastfm-albumart/Makefile.am
create mode 100644 src/metadata/lastfm-albumart/grl-lastfm-albumart.c
create mode 100644 src/metadata/lastfm-albumart/grl-lastfm-albumart.h
create mode 100644 src/metadata/lastfm-albumart/grl-lastfm-albumart.xml
create mode 100644 src/metadata/local-metadata/Makefile.am
create mode 100644 src/metadata/local-metadata/grl-local-metadata.c
create mode 100644 src/metadata/local-metadata/grl-local-metadata.h
create mode 100644 src/metadata/local-metadata/grl-local-metadata.xml
create mode 100644 src/metadata/metadata-store/Makefile.am
create mode 100644 src/metadata/metadata-store/grl-metadata-store.c
create mode 100644 src/metadata/metadata-store/grl-metadata-store.h
create mode 100644 src/metadata/metadata-store/grl-metadata-store.xml
delete mode 100644 src/podcasts/Makefile.am
delete mode 100644 src/podcasts/TODO
delete mode 100644 src/podcasts/grl-podcasts.c
delete mode 100644 src/podcasts/grl-podcasts.h
delete mode 100644 src/podcasts/grl-podcasts.xml
delete mode 100644 src/shoutcast/Makefile.am
delete mode 100644 src/shoutcast/grl-shoutcast.c
delete mode 100644 src/shoutcast/grl-shoutcast.h
delete mode 100644 src/shoutcast/grl-shoutcast.xml
delete mode 100644 src/tracker/Makefile.am
delete mode 100644 src/tracker/grl-tracker.c
delete mode 100644 src/tracker/grl-tracker.h
delete mode 100644 src/tracker/grl-tracker.xml
delete mode 100644 src/upnp/Makefile.am
delete mode 100644 src/upnp/grl-upnp.c
delete mode 100644 src/upnp/grl-upnp.h
delete mode 100644 src/upnp/grl-upnp.xml
delete mode 100644 src/vimeo/Makefile.am
delete mode 100644 src/vimeo/grl-vimeo.c
delete mode 100644 src/vimeo/grl-vimeo.h
delete mode 100644 src/vimeo/grl-vimeo.xml
delete mode 100644 src/vimeo/gvimeo.c
delete mode 100644 src/vimeo/gvimeo.h
delete mode 100644 src/youtube/Makefile.am
delete mode 100644 src/youtube/TODO
delete mode 100644 src/youtube/grl-youtube.c
delete mode 100644 src/youtube/grl-youtube.h
delete mode 100644 src/youtube/grl-youtube.xml
diff --git a/src/apple-trailers/Makefile.am b/src/apple-trailers/Makefile.am
deleted file mode 100644
index 0195de5..0000000
--- a/src/apple-trailers/Makefile.am
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Juan A. Suarez Romero <jasuarez igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlappletrailers.la
-
-libgrlappletrailers_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GRLNET_CFLAGS) \
- $(XML_CFLAGS)
-
-libgrlappletrailers_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GRLNET_LIBS) \
- $(XML_LIBS)
-
-libgrlappletrailers_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlappletrailers_la_SOURCES = \
- grl-apple-trailers.c \
- grl-apple-trailers.h
-
-libdir = $(GRL_PLUGINS_DIR)
-appletrailersxmldir = $(GRL_PLUGINS_CONF_DIR)
-appletrailersxml_DATA = $(APPLE_TRAILERS_PLUGIN_ID).xml
-
-EXTRA_DIST = $(appletrailersxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/apple-trailers/grl-apple-trailers.c b/src/apple-trailers/grl-apple-trailers.c
deleted file mode 100644
index b81748b..0000000
--- a/src/apple-trailers/grl-apple-trailers.c
+++ /dev/null
@@ -1,611 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- * Copyright (C) 2011 Intel Corporation.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
-#include <net/grl-net.h>
-#include <libxml/xpath.h>
-
-#include "grl-apple-trailers.h"
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT apple_trailers_log_domain
-GRL_LOG_DOMAIN_STATIC(apple_trailers_log_domain);
-
-/* ---- Apple Trailers Service ---- */
-
-#define APPLE_TRAILERS_CURRENT_SD \
- "http://trailers.apple.com/trailers/home/xml/current_480p.xml"
-
-#define APPLE_TRAILERS_CURRENT_HD \
- "http://trailers.apple.com/trailers/home/xml/current_720p.xml"
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID APPLE_TRAILERS_PLUGIN_ID
-
-#define SOURCE_ID "grl-apple-trailers"
-#define SOURCE_NAME "Apple Movie Trailers"
-#define SOURCE_DESC "A plugin for browsing Apple Movie Trailers"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-typedef struct {
- GrlMediaSourceBrowseSpec *bs;
- xmlDocPtr xml_doc;
- xmlNodePtr xml_entries;
- gboolean cancelled;
-} OperationData;
-
-enum {
- PROP_0,
- PROP_HD,
- PROP_LARGE_POSTER,
-};
-
-struct _GrlAppleTrailersSourcePriv {
- GrlNetWc *wc;
- GCancellable *cancellable;
- gboolean hd;
- gboolean large_poster;
-};
-
-#define GRL_APPLE_TRAILERS_SOURCE_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_APPLE_TRAILERS_SOURCE_TYPE, \
- GrlAppleTrailersSourcePriv))
-
-static GrlAppleTrailersSource *grl_apple_trailers_source_new (gboolean hd,
- gboolean xlarge);
-
-gboolean grl_apple_trailers_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-static const GList *grl_apple_trailers_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_apple_trailers_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-
-static void grl_apple_trailers_source_cancel (GrlMediaSource *source,
- guint operation_id);
-
-/* =================== Apple Trailers Plugin =============== */
-
-gboolean
-grl_apple_trailers_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GrlAppleTrailersSource *source;
- gboolean hd = FALSE;
- gboolean xlarge = FALSE;
-
- GRL_LOG_DOMAIN_INIT (apple_trailers_log_domain, "apple-trailers");
-
- GRL_DEBUG ("apple_trailers_plugin_init");
-
- for (; configs; configs = g_list_next (configs)) {
- GrlConfig *config;
- gchar *definition, *poster_size;
-
- config = GRL_CONFIG (configs->data);
- definition = grl_config_get_string (config, "definition");
- if (definition) {
- if (*definition != '\0') {
- if (g_str_equal (definition, "hd")) {
- hd = TRUE;
- }
- }
- g_free (definition);
- }
-
- poster_size = grl_config_get_string (config, "poster-size");
- if (poster_size) {
- if (*poster_size != '\0') {
- if (g_str_equal (poster_size, "xlarge")) {
- xlarge = TRUE;
- }
- }
- g_free (poster_size);
- }
- }
-
- source = grl_apple_trailers_source_new (hd, xlarge);
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_apple_trailers_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== AppleTrailers GObject ================ */
-
-static GrlAppleTrailersSource *
-grl_apple_trailers_source_new (gboolean high_definition,
- gboolean xlarge)
-{
- GrlAppleTrailersSource *source;
-
- GRL_DEBUG ("grl_apple_trailers_source_new%s%s",
- high_definition ? " (HD)" : "",
- xlarge ? " (X-large poster)" : "");
- source = g_object_new (GRL_APPLE_TRAILERS_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- "high-definition", high_definition,
- "large-poster", xlarge,
- NULL);
-
- return source;
-}
-
-G_DEFINE_TYPE (GrlAppleTrailersSource, grl_apple_trailers_source, GRL_TYPE_MEDIA_SOURCE);
-
-static void
-grl_apple_trailers_source_finalize (GObject *object)
-{
- GrlAppleTrailersSource *self;
-
- self = GRL_APPLE_TRAILERS_SOURCE (object);
-
- if (self->priv->wc)
- g_object_unref (self->priv->wc);
-
- if (self->priv->cancellable
- && G_IS_CANCELLABLE (self->priv->cancellable))
- g_object_unref (self->priv->cancellable);
-
- G_OBJECT_CLASS (grl_apple_trailers_source_parent_class)->finalize (object);
-}
-
-static void
-grl_apple_trailers_source_set_property (GObject *object,
- guint propid,
- const GValue *value,
- GParamSpec *pspec)
-{
- GrlAppleTrailersSource *self;
- self = GRL_APPLE_TRAILERS_SOURCE (object);
-
- switch (propid) {
- case PROP_HD:
- self->priv->hd = g_value_get_boolean (value);
- break;
- case PROP_LARGE_POSTER:
- self->priv->large_poster = g_value_get_boolean (value);
- break;
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
- }
-}
-
-static void
-grl_apple_trailers_source_class_init (GrlAppleTrailersSourceClass * klass)
-{
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- GObjectClass *g_class = G_OBJECT_CLASS (klass);
- source_class->browse = grl_apple_trailers_source_browse;
- source_class->cancel = grl_apple_trailers_source_cancel;
- metadata_class->supported_keys = grl_apple_trailers_source_supported_keys;
- g_class->finalize = grl_apple_trailers_source_finalize;
- g_class->set_property = grl_apple_trailers_source_set_property;
-
- g_object_class_install_property (g_class,
- PROP_HD,
- g_param_spec_boolean ("high-definition",
- "hd",
- "Hi/Low definition videos",
- TRUE,
- G_PARAM_WRITABLE
- | G_PARAM_CONSTRUCT_ONLY
- | G_PARAM_STATIC_NAME));
-
- g_object_class_install_property (g_class,
- PROP_LARGE_POSTER,
- g_param_spec_boolean ("large-poster",
- "xlarge",
- "Pick large poster",
- TRUE,
- G_PARAM_WRITABLE
- | G_PARAM_CONSTRUCT_ONLY
- | G_PARAM_STATIC_NAME));
-
- g_type_class_add_private (klass, sizeof (GrlAppleTrailersSourcePriv));
-}
-
-static void
-grl_apple_trailers_source_init (GrlAppleTrailersSource *source)
-{
- source->priv = GRL_APPLE_TRAILERS_SOURCE_GET_PRIVATE (source);
- source->priv->hd = TRUE;
-}
-
-/* ==================== Private ==================== */
-
-static gchar *
-get_node_value (xmlNodePtr node, const gchar *node_id)
-{
- gchar *value;
- xmlXPathContextPtr xpath_ctx;
- xmlXPathObjectPtr xpath_res;
-
- xpath_ctx = xmlXPathNewContext (node->doc);
- if (!xpath_ctx) {
- return NULL;
- }
-
- xpath_res = xmlXPathEvalExpression ((xmlChar *) node_id, xpath_ctx);
- if (!xpath_res) {
- xmlXPathFreeContext (xpath_ctx);
- return NULL;
- }
-
- if (xpath_res->nodesetval->nodeTab) {
- value =
- (gchar *) xmlNodeListGetString (node->doc,
- xpath_res->nodesetval->nodeTab[0]->xmlChildrenNode,
- 1);
- } else {
- value = NULL;
- }
-
- xmlXPathFreeObject (xpath_res);
- xmlXPathFreeContext (xpath_ctx);
-
- return value;
-}
-
-static gint
-runtime_to_seconds (const gchar *runtime)
-{
- gchar **items;
- gint seconds;
-
- if (!runtime) {
- return 0;
- }
-
- seconds = 0;
- items = g_strsplit (runtime, ":", -1);
- if (items && items[0] && items[1])
- seconds = 3600 * atoi (items[0]) + 60 * atoi (items[1]);
- g_strfreev (items);
-
- return seconds;
-}
-static GrlMedia *
-build_media_from_movie (xmlNodePtr node, gboolean xlarge)
-{
- GrlMedia * media;
- gchar *movie_author;
- gchar *movie_date;
- gchar *movie_description;
- gchar *movie_duration;
- gchar *movie_genre;
- gchar *movie_id;
- gchar *movie_thumbnail;
- gchar *movie_title;
- gchar *movie_url;
- gchar *movie_rating;
- gchar *movie_studio;
- gchar *movie_copyright;
-
- media = grl_media_video_new ();
-
- movie_id = (gchar *) xmlGetProp (node, (const xmlChar *) "id");
-
- /* HACK: as get_node_value applies xpath expression from root node, but we
- want to do from current node, dup the node and mark it as root node */
-
- xmlDocPtr xml_doc = xmlNewDoc ((const xmlChar *) "1.0");
- xmlNodePtr node_dup = xmlCopyNode (node, 1);
- xmlDocSetRootElement (xml_doc, node_dup);
- movie_author = get_node_value (node_dup, "/movieinfo/info/director");
- movie_date = get_node_value (node_dup, "/movieinfo/info/releasedate");
- movie_description = get_node_value (node_dup, "/movieinfo/info/description");
- movie_duration = get_node_value (node_dup, "/movieinfo/info/runtime");
- movie_title = get_node_value (node_dup, "/movieinfo/info/title");
- movie_genre = get_node_value (node_dup, "/movieinfo/genre/name");
- if (xlarge)
- movie_thumbnail = get_node_value (node_dup, "/movieinfo/poster/xlarge");
- else
- movie_thumbnail = get_node_value (node_dup, "/movieinfo/poster/location");
- movie_url = get_node_value (node_dup, "/movieinfo/preview/large");
- movie_rating = get_node_value (node_dup, "/movieinfo/info/rating");
- movie_studio = get_node_value (node_dup, "/movieinfo/info/studio");
- movie_copyright = get_node_value (node_dup, "/movieinfo/info/copyright");
- xmlFreeDoc (xml_doc);
-
- grl_media_set_id (media, movie_id);
- grl_media_set_author (media, movie_author);
- grl_media_set_date (media, movie_date);
- grl_media_set_description (media, movie_description);
- grl_media_set_duration (media, runtime_to_seconds (movie_duration));
- grl_media_set_title (media, movie_title);
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_GENRE,
- movie_genre);
- grl_media_set_thumbnail (media, movie_thumbnail);
- grl_media_set_url (media, movie_url);
- grl_media_set_certificate (media, movie_rating);
- grl_media_set_studio (media, movie_studio);
-
- /* FIXME: Translation */
- grl_media_set_license (media, movie_copyright);
-
- g_free (movie_id);
- g_free (movie_author);
- g_free (movie_date);
- g_free (movie_description);
- g_free (movie_duration);
- g_free (movie_title);
- g_free (movie_genre);
- g_free (movie_thumbnail);
- g_free (movie_url);
- g_free (movie_rating);
- g_free (movie_studio);
- g_free (movie_copyright);
-
- return media;
-}
-
-static gboolean
-send_movie_info (OperationData *op_data)
-{
- GrlMedia *media;
- gboolean last = FALSE;
-
- if (op_data->cancelled) {
- op_data->bs->callback (op_data->bs->source,
- op_data->bs->browse_id,
- NULL,
- 0,
- op_data->bs->user_data,
- NULL);
- last = TRUE;
- } else {
- GrlAppleTrailersSource *source =
- GRL_APPLE_TRAILERS_SOURCE (op_data->bs->source);
-
- media = build_media_from_movie (op_data->xml_entries,
- source->priv->large_poster);
- last =
- !op_data->xml_entries->next ||
- op_data->bs->count == 1;
-
- op_data->bs->callback (op_data->bs->source,
- op_data->bs->browse_id,
- media,
- last? 0: -1,
- op_data->bs->user_data,
- NULL);
- op_data->xml_entries = op_data->xml_entries->next;
- if (!last)
- op_data->bs->count--;
- }
-
- if (last) {
- xmlFreeDoc (op_data->xml_doc);
- g_slice_free (OperationData, op_data);
- }
-
- return !last;
-}
-
-static void
-xml_parse_result (const gchar *str, OperationData *op_data)
-{
- GError *error = NULL;
- xmlNodePtr node;
-
- if (op_data->cancelled || op_data->bs->count == 0) {
- goto finalize;
- }
-
- op_data->xml_doc = xmlReadMemory (str, xmlStrlen ((xmlChar*) str), NULL, NULL,
- XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- if (!op_data->xml_doc) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Failed to parse response");
- goto finalize;
- }
-
- node = xmlDocGetRootElement (op_data->xml_doc);
- if (!node || !node->xmlChildrenNode) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Empty response from Apple Trailers");
- goto finalize;
- }
-
- node = node->xmlChildrenNode;
-
- /* Skip elements */
- while (node && op_data->bs->skip > 0) {
- node = node->next;
- op_data->bs->skip--;
- }
-
- if (!node) {
- goto finalize;
- } else {
- op_data->xml_entries = node;
- g_idle_add ((GSourceFunc) send_movie_info, op_data);
- }
-
- return;
-
- finalize:
- op_data->bs->callback (op_data->bs->source,
- op_data->bs->browse_id,
- NULL,
- 0,
- op_data->bs->user_data,
- error);
-
- if (op_data->xml_doc) {
- xmlFreeDoc (op_data->xml_doc);
- }
-
- if (error) {
- g_error_free (error);
- }
-
- g_slice_free (OperationData, op_data);
-}
-
-static void
-read_done_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- GError *error = NULL;
- GError *wc_error = NULL;
- OperationData *op_data = (OperationData *) user_data;
- gchar *content = NULL;
-
- if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
- res,
- &content,
- NULL,
- &wc_error)) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Failed to connect Apple Trailers: '%s'",
- wc_error->message);
- op_data->bs->callback (op_data->bs->source,
- op_data->bs->browse_id,
- NULL,
- 0,
- op_data->bs->user_data,
- error);
- g_error_free (wc_error);
- g_error_free (error);
- g_slice_free (OperationData, op_data);
-
- return;
- }
-
- xml_parse_result (content, op_data);
-}
-
-static void
-read_url_async (GrlAppleTrailersSource *source,
- const gchar *url,
- gpointer user_data)
-{
- if (!source->priv->wc)
- source->priv->wc = grl_net_wc_new ();
-
- source->priv->cancellable = g_cancellable_new ();
-
- GRL_DEBUG ("Opening '%s'", url);
- grl_net_wc_request_async (source->priv->wc,
- url,
- source->priv->cancellable,
- read_done_cb,
- user_data);
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_apple_trailers_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_AUTHOR,
- GRL_METADATA_KEY_DATE,
- GRL_METADATA_KEY_DESCRIPTION,
- GRL_METADATA_KEY_DURATION,
- GRL_METADATA_KEY_GENRE,
- GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_THUMBNAIL,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_URL,
- GRL_METADATA_KEY_CERTIFICATE,
- GRL_METADATA_KEY_STUDIO,
- GRL_METADATA_KEY_LICENSE,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_apple_trailers_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- GrlAppleTrailersSource *at_source = GRL_APPLE_TRAILERS_SOURCE (source);
- OperationData *op_data;
-
- GRL_DEBUG ("grl_apple_trailers_source_browse");
-
- op_data = g_slice_new0 (OperationData);
- op_data->bs = bs;
- grl_media_source_set_operation_data (source, bs->browse_id, op_data);
-
- if (at_source->priv->hd) {
- read_url_async (at_source, APPLE_TRAILERS_CURRENT_HD, op_data);
- } else {
- read_url_async (at_source, APPLE_TRAILERS_CURRENT_SD, op_data);
- }
-}
-
-static void
-grl_apple_trailers_source_cancel (GrlMediaSource *source, guint operation_id)
-{
- OperationData *op_data;
- GrlAppleTrailersSourcePriv *priv;
-
- GRL_DEBUG ("grl_apple_trailers_source_cancel");
-
- priv = GRL_APPLE_TRAILERS_SOURCE_GET_PRIVATE (source);
- if (priv->cancellable && G_IS_CANCELLABLE (priv->cancellable))
- g_cancellable_cancel (priv->cancellable);
- priv->cancellable = NULL;
-
- if (priv->wc)
- grl_net_wc_flush_delayed_requests (priv->wc);
-
- op_data = (OperationData *) grl_media_source_get_operation_data (source, operation_id);
-
- if (op_data) {
- op_data->cancelled = TRUE;
- }
-}
diff --git a/src/apple-trailers/grl-apple-trailers.h b/src/apple-trailers/grl-apple-trailers.h
deleted file mode 100644
index 74f8f9a..0000000
--- a/src/apple-trailers/grl-apple-trailers.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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_APPLE_TRAILERS_SOURCE_H_
-#define _GRL_APPLE_TRAILERS_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_APPLE_TRAILERS_SOURCE_TYPE \
- (grl_apple_trailers_source_get_type ())
-
-#define GRL_APPLE_TRAILERS_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_APPLE_TRAILERS_SOURCE_TYPE, \
- GrlAppleTrailersSource))
-
-#define GRL_IS_APPLE_TRAILERS_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_APPLE_TRAILERS_SOURCE_TYPE))
-
-#define GRL_APPLE_TRAILERS_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_APPLE_TRAILERS_SOURCE_TYPE, \
- GrlAppleTrailersSourceClass))
-
-#define GRL_IS_APPLE_TRAILERS_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_APPLE_TRAILERS_SOURCE_TYPE))
-
-#define GRL_APPLE_TRAILERS_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_APPLE_TRAILERS_SOURCE_TYPE, \
- GrlAppleTrailersSourceClass))
-
-typedef struct _GrlAppleTrailersSource GrlAppleTrailersSource;
-typedef struct _GrlAppleTrailersSourcePriv GrlAppleTrailersSourcePriv;
-
-struct _GrlAppleTrailersSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlAppleTrailersSourcePriv *priv;
-
-};
-
-typedef struct _GrlAppleTrailersSourceClass GrlAppleTrailersSourceClass;
-
-struct _GrlAppleTrailersSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_apple_trailers_source_get_type (void);
-
-#endif /* _GRL_APPLE_TRAILERS_SOURCE_H_ */
diff --git a/src/apple-trailers/grl-apple-trailers.xml b/src/apple-trailers/grl-apple-trailers.xml
deleted file mode 100644
index 9fd929a..0000000
--- a/src/apple-trailers/grl-apple-trailers.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Apple Movie Trailers</name>
- <description>A plugin for browsing Apple Movie Trailers</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/bookmarks/Makefile.am b/src/bookmarks/Makefile.am
deleted file mode 100644
index 0a031f5..0000000
--- a/src/bookmarks/Makefile.am
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Iago Toral Quiroga <itoral igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlbookmarks.la
-
-libgrlbookmarks_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(SQLITE_CFLAGS)
-
-libgrlbookmarks_la_LIBADD = \
- $(DEPS_LIBS) \
- $(SQLITE_LIBS)
-
-libgrlbookmarks_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlbookmarks_la_SOURCES = grl-bookmarks.c grl-bookmarks.h
-
-libdir=$(GRL_PLUGINS_DIR)
-bookmarksxmldir = $(GRL_PLUGINS_CONF_DIR)
-bookmarksxml_DATA = $(BOOKMARKS_PLUGIN_ID).xml
-
-EXTRA_DIST = $(bookmarksxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/bookmarks/grl-bookmarks.c b/src/bookmarks/grl-bookmarks.c
deleted file mode 100644
index 1c7de10..0000000
--- a/src/bookmarks/grl-bookmarks.c
+++ /dev/null
@@ -1,888 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
-#include <sqlite3.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "grl-bookmarks.h"
-
-#define GRL_BOOKMARKS_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_BOOKMARKS_SOURCE_TYPE, \
- GrlBookmarksPrivate))
-
-#define GRL_ROOT_TITLE "Bookmarks"
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT bookmarks_log_domain
-GRL_LOG_DOMAIN_STATIC(bookmarks_log_domain);
-
-/* --- Database --- */
-
-#define GRL_SQL_DB ".grl-bookmarks"
-
-#define GRL_SQL_CREATE_TABLE_BOOKMARKS \
- "CREATE TABLE IF NOT EXISTS bookmarks (" \
- "id INTEGER PRIMARY KEY AUTOINCREMENT," \
- "parent INTEGER REFERENCES bookmarks (id)," \
- "type INTEGER," \
- "url TEXT," \
- "title TEXT," \
- "date TEXT," \
- "mime TEXT," \
- "desc TEXT)"
-
-#define GRL_SQL_GET_BOOKMARKS_BY_PARENT \
- "SELECT b1.*, count(b2.parent <> '') " \
- "FROM bookmarks b1 LEFT OUTER JOIN bookmarks b2 " \
- " ON b1.id = b2.parent " \
- "WHERE b1.parent='%s' " \
- "GROUP BY b1.id " \
- "LIMIT %u OFFSET %u"
-
-#define GRL_SQL_GET_BOOKMARK_BY_ID \
- "SELECT b1.*, count(b2.parent <> '') " \
- "FROM bookmarks b1 LEFT OUTER JOIN bookmarks b2 " \
- " ON b1.id = b2.parent " \
- "WHERE b1.id='%s' " \
- "GROUP BY b1.id " \
- "LIMIT 1"
-
-#define GRL_SQL_STORE_BOOKMARK \
- "INSERT INTO bookmarks " \
- "(parent, type, url, title, date, mime, desc) " \
- "VALUES (?, ?, ?, ?, ?, ?, ?)"
-
-#define GRL_SQL_REMOVE_BOOKMARK \
- "DELETE FROM bookmarks " \
- "WHERE id='%s' or parent='%s'"
-
-#define GRL_SQL_REMOVE_ORPHAN \
- "DELETE FROM bookmarks " \
- "WHERE id in ( " \
- " SELECT DISTINCT id FROM bookmarks " \
- " WHERE parent NOT IN ( " \
- " SELECT DISTINCT id FROM bookmarks) " \
- " and parent <> 0)"
-
-#define GRL_SQL_GET_BOOKMARKS_BY_TEXT \
- "SELECT b1.*, count(b2.parent <> '') " \
- "FROM bookmarks b1 LEFT OUTER JOIN bookmarks b2 " \
- " ON b1.id = b2.parent " \
- "WHERE (b1.title LIKE '%%%s%%' OR b1.desc LIKE '%%%s%%') " \
- " AND b1.type = 1 " \
- "GROUP BY b1.id " \
- "LIMIT %u OFFSET %u"
-
-#define GRL_SQL_GET_BOOKMARKS_BY_QUERY \
- "SELECT b1.*, count(b2.parent <> '') " \
- "FROM bookmarks b1 LEFT OUTER JOIN bookmarks b2 " \
- " ON b1.id = b2.parent " \
- "WHERE %s " \
- "GROUP BY b1.id " \
- "LIMIT %u OFFSET %u"
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID BOOKMARKS_PLUGIN_ID
-
-#define SOURCE_ID "grl-bookmarks"
-#define SOURCE_NAME "Bookmarks"
-#define SOURCE_DESC "A source for organizing media bookmarks"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-
-enum {
- BOOKMARK_TYPE_CATEGORY = 0,
- BOOKMARK_TYPE_STREAM,
-};
-
-enum {
- BOOKMARK_ID = 0,
- BOOKMARK_PARENT,
- BOOKMARK_TYPE,
- BOOKMARK_URL,
- BOOKMARK_TITLE,
- BOOKMARK_DATE,
- BOOKMARK_MIME,
- BOOKMARK_DESC,
- BOOKMARK_CHILDCOUNT
-};
-
-struct _GrlBookmarksPrivate {
- sqlite3 *db;
- gboolean notify_changes;
-};
-
-typedef struct {
- GrlMediaSource *source;
- guint operation_id;
- const gchar *media_id;
- guint skip;
- guint count;
- GrlMediaSourceResultCb callback;
- guint error_code;
- gboolean is_query;
- gpointer user_data;
-} OperationSpec;
-
-static GrlBookmarksSource *grl_bookmarks_source_new (void);
-
-static void grl_bookmarks_source_finalize (GObject *plugin);
-
-static const GList *grl_bookmarks_source_supported_keys (GrlMetadataSource *source);
-static GrlSupportedOps grl_bookmarks_source_supported_operations (GrlMetadataSource *metadata_source);
-
-static void grl_bookmarks_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-static void grl_bookmarks_source_query (GrlMediaSource *source,
- GrlMediaSourceQuerySpec *qs);
-static void grl_bookmarks_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-static void grl_bookmarks_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms);
-static void grl_bookmarks_source_store (GrlMediaSource *source,
- GrlMediaSourceStoreSpec *ss);
-static void grl_bookmarks_source_remove (GrlMediaSource *source,
- GrlMediaSourceRemoveSpec *rs);
-
-static gboolean grl_bookmarks_source_notify_change_start (GrlMediaSource *source,
- GError **error);
-
-static gboolean grl_bookmarks_source_notify_change_stop (GrlMediaSource *source,
- GError **error);
-
- /* =================== Bookmarks Plugin =============== */
-
- static gboolean
- grl_bookmarks_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
- {
- GRL_LOG_DOMAIN_INIT (bookmarks_log_domain, "bookmarks");
-
- GRL_DEBUG ("grl_bookmarks_plugin_init");
-
- GrlBookmarksSource *source = grl_bookmarks_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
- }
-
- GRL_PLUGIN_REGISTER (grl_bookmarks_plugin_init,
- NULL,
- PLUGIN_ID);
-
- /* ================== Bookmarks GObject ================ */
-
- static GrlBookmarksSource *
- grl_bookmarks_source_new (void)
- {
- GRL_DEBUG ("grl_bookmarks_source_new");
- return g_object_new (GRL_BOOKMARKS_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
- }
-
- static void
- grl_bookmarks_source_class_init (GrlBookmarksSourceClass * klass)
- {
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
-
- gobject_class->finalize = grl_bookmarks_source_finalize;
-
- metadata_class->supported_operations =
- grl_bookmarks_source_supported_operations;
-
- source_class->browse = grl_bookmarks_source_browse;
- source_class->search = grl_bookmarks_source_search;
- source_class->query = grl_bookmarks_source_query;
- source_class->store = grl_bookmarks_source_store;
- source_class->remove = grl_bookmarks_source_remove;
- source_class->metadata = grl_bookmarks_source_metadata;
- source_class->notify_change_start = grl_bookmarks_source_notify_change_start;
- source_class->notify_change_stop = grl_bookmarks_source_notify_change_stop;
-
- metadata_class->supported_keys = grl_bookmarks_source_supported_keys;
-
- g_type_class_add_private (klass, sizeof (GrlBookmarksPrivate));
-}
-
-static void
-grl_bookmarks_source_init (GrlBookmarksSource *source)
-{
- gint r;
- const gchar *home;
- gchar *db_path;
- gchar *sql_error = NULL;
-
- source->priv = GRL_BOOKMARKS_GET_PRIVATE (source);
-
- home = g_getenv ("HOME");
- if (!home) {
- GRL_WARNING ("$HOME not set, cannot open database");
- return;
- }
-
- GRL_DEBUG ("Opening database connection...");
- db_path = g_strconcat (home, G_DIR_SEPARATOR_S, GRL_SQL_DB, NULL);
- r = sqlite3_open (db_path, &source->priv->db);
- if (r) {
- g_critical ("Failed to open database '%s': %s",
- db_path, sqlite3_errmsg (source->priv->db));
- sqlite3_close (source->priv->db);
- return;
- }
- GRL_DEBUG (" OK");
-
- GRL_DEBUG ("Checking database tables...");
- r = sqlite3_exec (source->priv->db, GRL_SQL_CREATE_TABLE_BOOKMARKS,
- NULL, NULL, &sql_error);
-
- if (r) {
- if (sql_error) {
- GRL_WARNING ("Failed to create database tables: %s", sql_error);
- sqlite3_free (sql_error);
- sql_error = NULL;
- } else {
- GRL_WARNING ("Failed to create database tables.");
- }
- sqlite3_close (source->priv->db);
- return;
- }
- GRL_DEBUG (" OK");
-
- g_free (db_path);
-}
-
-G_DEFINE_TYPE (GrlBookmarksSource, grl_bookmarks_source, GRL_TYPE_MEDIA_SOURCE);
-
-static void
-grl_bookmarks_source_finalize (GObject *object)
-{
- GrlBookmarksSource *source;
-
- GRL_DEBUG ("grl_bookmarks_source_finalize");
-
- source = GRL_BOOKMARKS_SOURCE (object);
-
- sqlite3_close (source->priv->db);
-
- G_OBJECT_CLASS (grl_bookmarks_source_parent_class)->finalize (object);
-}
-
-/* ======================= Utilities ==================== */
-
-static gboolean
-mime_is_video (const gchar *mime)
-{
- return mime && strstr (mime, "video") != NULL;
-}
-
-static gboolean
-mime_is_audio (const gchar *mime)
-{
- return mime && strstr (mime, "audio") != NULL;
-}
-
-static GrlMedia *
-build_media_from_stmt (GrlMedia *content, sqlite3_stmt *sql_stmt)
-{
- GrlMedia *media = NULL;
- gchar *id;
- gchar *title;
- gchar *url;
- gchar *desc;
- gchar *date;
- gchar *mime;
- guint type;
- guint childcount;
-
- if (content) {
- media = content;
- }
-
- id = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_ID);
- title = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_TITLE);
- url = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_URL);
- desc = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_DESC);
- date = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_DATE);
- mime = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_MIME);
- type = (guint) sqlite3_column_int (sql_stmt, BOOKMARK_TYPE);
- childcount = (guint) sqlite3_column_int (sql_stmt, BOOKMARK_CHILDCOUNT);
-
- if (!media) {
- if (type == BOOKMARK_TYPE_CATEGORY) {
- media = GRL_MEDIA (grl_media_box_new ());
- } else if (mime_is_audio (mime)) {
- media = GRL_MEDIA (grl_media_new ());
- } else if (mime_is_video (mime)) {
- media = GRL_MEDIA (grl_media_new ());
- } else {
- media = GRL_MEDIA (grl_media_new ());
- }
- }
-
- grl_media_set_id (media, id);
- grl_media_set_title (media, title);
- if (url) {
- grl_media_set_url (media, url);
- }
- if (desc) {
- grl_media_set_description (media, desc);
- }
- if (date) {
- grl_media_set_date (media, date);
- }
-
- if (type == BOOKMARK_TYPE_CATEGORY) {
- grl_media_box_set_childcount (GRL_MEDIA_BOX (media), childcount);
- }
-
- return media;
-}
-
-static void
-bookmark_metadata (GrlMediaSourceMetadataSpec *ms)
-{
- gint r;
- sqlite3_stmt *sql_stmt = NULL;
- sqlite3 *db;
- GError *error = NULL;
- gchar *sql;
- const gchar *id;
-
- GRL_DEBUG ("bookmark_metadata");
-
- db = GRL_BOOKMARKS_SOURCE (ms->source)->priv->db;
-
- id = grl_media_get_id (ms->media);
- if (!id) {
- /* Root category: special case */
- grl_media_set_title (ms->media, GRL_ROOT_TITLE);
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- return;
- }
-
- sql = g_strdup_printf (GRL_SQL_GET_BOOKMARK_BY_ID, id);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to get bookmark: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Failed to get bookmark metadata");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- return;
- }
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- if (r == SQLITE_ROW) {
- build_media_from_stmt (ms->media, sql_stmt);
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- } else {
- GRL_WARNING ("Failed to get bookmark: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Failed to get bookmark metadata");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- }
-
- sqlite3_finalize (sql_stmt);
-}
-
-static void
-produce_bookmarks_from_sql (OperationSpec *os, const gchar *sql)
-{
- gint r;
- sqlite3_stmt *sql_stmt = NULL;
- sqlite3 *db;
- GrlMedia *media;
- GError *error = NULL;
- GList *medias = NULL;
- guint count = 0;
- GList *iter;
-
- GRL_DEBUG ("produce_bookmarks_from_sql");
-
- GRL_DEBUG ("%s", sql);
- db = GRL_BOOKMARKS_SOURCE (os->source)->priv->db;
- r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to retrieve bookmarks: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to retrieve bookmarks list");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
- goto free_resources;
- }
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- while (r == SQLITE_ROW) {
- media = build_media_from_stmt (NULL, sql_stmt);
- medias = g_list_prepend (medias, media);
- count++;
- r = sqlite3_step (sql_stmt);
- }
-
- if (r != SQLITE_DONE) {
- GRL_WARNING ("Failed to retrieve bookmarks: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to retrieve bookmarks list");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
- goto free_resources;
- }
-
- if (count > 0) {
- medias = g_list_reverse (medias);
- iter = medias;
- while (iter) {
- media = GRL_MEDIA (iter->data);
- os->callback (os->source,
- os->operation_id,
- media,
- --count,
- os->user_data,
- NULL);
- iter = g_list_next (iter);
- }
- g_list_free (medias);
- } else {
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
- }
-
- free_resources:
- if (sql_stmt)
- sqlite3_finalize (sql_stmt);
-}
-
-static void
-produce_bookmarks_by_query (OperationSpec *os, const gchar *query)
-{
- gchar *sql;
- GRL_DEBUG ("produce_bookmarks_by_query");
- sql = g_strdup_printf (GRL_SQL_GET_BOOKMARKS_BY_QUERY,
- query, os->count, os->skip);
- produce_bookmarks_from_sql (os, sql);
- g_free (sql);
-}
-
-static void
-produce_bookmarks_by_text (OperationSpec *os, const gchar *text)
-{
- gchar *sql;
- GRL_DEBUG ("produce_bookmarks_by_text");
- sql = g_strdup_printf (GRL_SQL_GET_BOOKMARKS_BY_TEXT,
- text? text: "",
- text? text: "",
- os->count,
- os->skip);
- produce_bookmarks_from_sql (os, sql);
- g_free (sql);
-}
-
-static void
-produce_bookmarks_from_category (OperationSpec *os, const gchar *category_id)
-{
- gchar *sql;
- GRL_DEBUG ("produce_bookmarks_from_category");
- sql = g_strdup_printf (GRL_SQL_GET_BOOKMARKS_BY_PARENT,
- category_id, os->count, os->skip);
- produce_bookmarks_from_sql (os, sql);
- g_free (sql);
-}
-
-static void
-remove_bookmark (GrlBookmarksSource *bookmarks_source,
- const gchar *bookmark_id,
- GError **error)
-{
- gint r;
- gchar *sql_error;
- gchar *sql;
-
- GRL_DEBUG ("remove_bookmark");
-
- sql = g_strdup_printf (GRL_SQL_REMOVE_BOOKMARK, bookmark_id, bookmark_id);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_exec (bookmarks_source->priv->db, sql, NULL, NULL, &sql_error);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to remove bookmark '%s': %s", bookmark_id, sql_error);
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_REMOVE_FAILED,
- "Failed to remove bookmark");
- sqlite3_free (sql_error);
- }
-
- /* Remove orphan nodes from database */
- GRL_DEBUG ("%s", GRL_SQL_REMOVE_ORPHAN);
- r = sqlite3_exec (bookmarks_source->priv->db,
- GRL_SQL_REMOVE_ORPHAN,
- NULL, NULL, NULL);
-
- if (bookmarks_source->priv->notify_changes) {
- /* We can improve accuracy computing the parent container of removed
- element */
- grl_media_source_notify_change (GRL_MEDIA_SOURCE (bookmarks_source),
- NULL,
- GRL_CONTENT_REMOVED,
- TRUE);
- }
-}
-
-static void
-store_bookmark (GrlBookmarksSource *bookmarks_source,
- GrlMediaBox *parent,
- GrlMedia *bookmark,
- GError **error)
-{
- gint r;
- sqlite3_stmt *sql_stmt = NULL;
- const gchar *title;
- const gchar *url;
- const gchar *desc;
- GTimeVal now;
- const gchar *parent_id;
- const gchar *mime;
- gchar *date;
- guint type;
- gchar *id;
-
- GRL_DEBUG ("store_bookmark");
-
- title = grl_media_get_title (bookmark);
- url = grl_media_get_url (bookmark);
- desc = grl_media_get_description (bookmark);
- mime = grl_media_get_mime (bookmark);
- g_get_current_time (&now);
- date = g_time_val_to_iso8601 (&now);
-
- if (!parent) {
- parent_id = "0";
- } else {
- parent_id = grl_media_get_id (GRL_MEDIA (parent));
- }
- if (!parent_id) {
- parent_id = "0";
- }
-
- GRL_DEBUG ("%s", GRL_SQL_STORE_BOOKMARK);
- r = sqlite3_prepare_v2 (bookmarks_source->priv->db,
- GRL_SQL_STORE_BOOKMARK,
- strlen (GRL_SQL_STORE_BOOKMARK),
- &sql_stmt, NULL);
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to store bookmark '%s': %s", title,
- sqlite3_errmsg (bookmarks_source->priv->db));
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_STORE_FAILED,
- "Failed to store bookmark '%s'", title);
- return;
- }
-
- GRL_DEBUG ("URL: '%s'", url);
-
- if (GRL_IS_MEDIA_BOX (bookmark)) {
- type = BOOKMARK_TYPE_CATEGORY;
- } else {
- type = BOOKMARK_TYPE_STREAM;
- }
-
- sqlite3_bind_text (sql_stmt, BOOKMARK_PARENT, parent_id, -1, SQLITE_STATIC);
- sqlite3_bind_int (sql_stmt, BOOKMARK_TYPE, type);
- if (type == BOOKMARK_TYPE_STREAM) {
- sqlite3_bind_text (sql_stmt, BOOKMARK_URL, url, -1, SQLITE_STATIC);
- } else {
- sqlite3_bind_null (sql_stmt, BOOKMARK_URL);
- }
- sqlite3_bind_text (sql_stmt, BOOKMARK_TITLE, title, -1, SQLITE_STATIC);
- if (date) {
- sqlite3_bind_text (sql_stmt, BOOKMARK_DATE, date, -1, SQLITE_STATIC);
- } else {
- sqlite3_bind_null (sql_stmt, BOOKMARK_DATE);
- }
- if (mime) {
- sqlite3_bind_text (sql_stmt, BOOKMARK_MIME, mime, -1, SQLITE_STATIC);
- } else {
- sqlite3_bind_null (sql_stmt, BOOKMARK_MIME);
- }
- if (desc) {
- sqlite3_bind_text (sql_stmt, BOOKMARK_DESC, desc, -1, SQLITE_STATIC);
- } else {
- sqlite3_bind_null (sql_stmt, BOOKMARK_DESC);
- }
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- if (r != SQLITE_DONE) {
- GRL_WARNING ("Failed to store bookmark '%s': %s", title,
- sqlite3_errmsg (bookmarks_source->priv->db));
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_STORE_FAILED,
- "Failed to store bookmark '%s'", title);
- sqlite3_finalize (sql_stmt);
- return;
- }
-
- sqlite3_finalize (sql_stmt);
-
- id = g_strdup_printf ("%llu",
- sqlite3_last_insert_rowid (bookmarks_source->priv->db));
- grl_media_set_id (bookmark, id);
- g_free (id);
-
- if (bookmarks_source->priv->notify_changes) {
- grl_media_source_notify_change (GRL_MEDIA_SOURCE (bookmarks_source),
- GRL_MEDIA (parent),
- GRL_CONTENT_ADDED,
- FALSE);
- }
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_bookmarks_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_URL,
- GRL_METADATA_KEY_CHILDCOUNT,
- GRL_METADATA_KEY_DESCRIPTION,
- GRL_METADATA_KEY_DATE,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_bookmarks_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- GRL_DEBUG ("grl_bookmarks_source_browse");
-
- OperationSpec *os;
- GrlBookmarksSource *bookmarks_source;
- GError *error = NULL;
-
- bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
- if (!bookmarks_source->priv->db) {
- GRL_WARNING ("Can't execute operation: no database connection.");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "No database connection");
- bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, error);
- g_error_free (error);
- }
-
- /* Configure browse operation */
- os = g_slice_new0 (OperationSpec);
- os->source = bs->source;
- os->operation_id = bs->browse_id;
- os->media_id = grl_media_get_id (bs->container);
- os->count = bs->count;
- os->skip = bs->skip;
- os->callback = bs->callback;
- os->user_data = bs->user_data;
- os->error_code = GRL_CORE_ERROR_BROWSE_FAILED;
-
- produce_bookmarks_from_category (os, os->media_id ? os->media_id : "0");
- g_slice_free (OperationSpec, os);
-}
-
-static void
-grl_bookmarks_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss)
-{
- GRL_DEBUG ("grl_bookmarks_source_search");
-
- GrlBookmarksSource *bookmarks_source;
- OperationSpec *os;
- GError *error = NULL;
-
- bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
- if (!bookmarks_source->priv->db) {
- GRL_WARNING ("Can't execute operation: no database connection.");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_QUERY_FAILED,
- "No database connection");
- ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, error);
- g_error_free (error);
- }
-
- os = g_slice_new0 (OperationSpec);
- os->source = ss->source;
- os->operation_id = ss->search_id;
- os->count = ss->count;
- os->skip = ss->skip;
- os->callback = ss->callback;
- os->user_data = ss->user_data;
- os->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
- produce_bookmarks_by_text (os, ss->text);
- g_slice_free (OperationSpec, os);
-}
-
-static void
-grl_bookmarks_source_query (GrlMediaSource *source,
- GrlMediaSourceQuerySpec *qs)
-{
- GRL_DEBUG ("grl_bookmarks_source_query");
-
- GrlBookmarksSource *bookmarks_source;
- OperationSpec *os;
- GError *error = NULL;
-
- bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
- if (!bookmarks_source->priv->db) {
- GRL_WARNING ("Can't execute operation: no database connection.");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_QUERY_FAILED,
- "No database connection");
- qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
- g_error_free (error);
- }
-
- os = g_slice_new0 (OperationSpec);
- os->source = qs->source;
- os->operation_id = qs->query_id;
- os->count = qs->count;
- os->skip = qs->skip;
- os->callback = qs->callback;
- os->user_data = qs->user_data;
- os->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
- produce_bookmarks_by_query (os, qs->query);
- g_slice_free (OperationSpec, os);
-}
-
-static void
-grl_bookmarks_source_store (GrlMediaSource *source, GrlMediaSourceStoreSpec *ss)
-{
- GRL_DEBUG ("grl_bookmarks_source_store");
- /* FIXME: Try to guess bookmark mime somehow */
- GError *error = NULL;
- store_bookmark (GRL_BOOKMARKS_SOURCE (ss->source),
- ss->parent, ss->media, &error);
- ss->callback (ss->source, ss->parent, ss->media, ss->user_data, error);
- if (error) {
- g_error_free (error);
- }
-}
-
-static void grl_bookmarks_source_remove (GrlMediaSource *source,
- GrlMediaSourceRemoveSpec *rs)
-{
- GRL_DEBUG ("grl_bookmarks_source_remove");
- GError *error = NULL;
- remove_bookmark (GRL_BOOKMARKS_SOURCE (rs->source),
- rs->media_id, &error);
- rs->callback (rs->source, rs->media, rs->user_data, error);
- if (error) {
- g_error_free (error);
- }
-}
-
-static void
-grl_bookmarks_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- GRL_DEBUG ("grl_bookmarks_source_metadata");
-
- GrlBookmarksSource *bookmarks_source;
- GError *error = NULL;
-
- bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
- if (!bookmarks_source->priv->db) {
- GRL_WARNING ("Can't execute operation: no database connection.");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "No database connection");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- }
-
- bookmark_metadata (ms);
-}
-
-static GrlSupportedOps
-grl_bookmarks_source_supported_operations (GrlMetadataSource *metadata_source)
-{
- GrlSupportedOps caps;
- GrlBookmarksSource *source;
-
- source = GRL_BOOKMARKS_SOURCE (metadata_source);
- caps = GRL_OP_BROWSE | GRL_OP_METADATA | GRL_OP_SEARCH | GRL_OP_QUERY |
- GRL_OP_STORE | GRL_OP_STORE_PARENT | GRL_OP_REMOVE | GRL_OP_NOTIFY_CHANGE;
-
- return caps;
-}
-
-static gboolean
-grl_bookmarks_source_notify_change_start (GrlMediaSource *source,
- GError **error)
-{
- GrlBookmarksSource *bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
-
- bookmarks_source->priv->notify_changes = TRUE;
-
- return TRUE;
-}
-
-static gboolean
-grl_bookmarks_source_notify_change_stop (GrlMediaSource *source,
- GError **error)
-{
- GrlBookmarksSource *bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
-
- bookmarks_source->priv->notify_changes = FALSE;
-
- return TRUE;
-}
diff --git a/src/bookmarks/grl-bookmarks.h b/src/bookmarks/grl-bookmarks.h
deleted file mode 100644
index b5529b1..0000000
--- a/src/bookmarks/grl-bookmarks.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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_BOOKMARKS_SOURCE_H_
-#define _GRL_BOOKMARKS_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_BOOKMARKS_SOURCE_TYPE \
- (grl_bookmarks_source_get_type ())
-
-#define GRL_BOOKMARKS_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_BOOKMARKS_SOURCE_TYPE, \
- GrlBookmarksSource))
-
-#define GRL_IS_BOOKMARKS_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_BOOKMARKS_SOURCE_TYPE))
-
-#define GRL_BOOKMARKS_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_BOOKMARKS_SOURCE_TYPE, \
- GrlBookmarksSourceClass))
-
-#define GRL_IS_BOOKMARKS_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_BOOKMARKS_SOURCE_TYPE))
-
-#define GRL_BOOKMARKS_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_BOOKMARKS_SOURCE_TYPE, \
- GrlBookmarksSourceClass))
-
-typedef struct _GrlBookmarksPrivate GrlBookmarksPrivate;
-typedef struct _GrlBookmarksSource GrlBookmarksSource;
-
-struct _GrlBookmarksSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlBookmarksPrivate *priv;
-};
-
-typedef struct _GrlBookmarksSourceClass GrlBookmarksSourceClass;
-
-struct _GrlBookmarksSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_bookmarks_source_get_type (void);
-
-#endif /* _GRL_BOOKMARKS_SOURCE_H_ */
diff --git a/src/bookmarks/grl-bookmarks.xml b/src/bookmarks/grl-bookmarks.xml
deleted file mode 100644
index 74ef3ee..0000000
--- a/src/bookmarks/grl-bookmarks.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Bookmarks</name>
- <description>A plugin for organizing media bookmarks</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/fake-metadata/Makefile.am b/src/fake-metadata/Makefile.am
deleted file mode 100644
index e0a1b81..0000000
--- a/src/fake-metadata/Makefile.am
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Iago Toral Quiroga <itoral igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlfakemetadata.la
-
-libgrlfakemetadata_la_CFLAGS = \
- $(DEPS_CFLAGS)
-
-libgrlfakemetadata_la_LIBADD = \
- $(DEPS_LIBS)
-
-libgrlfakemetadata_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlfakemetadata_la_SOURCES = grl-fake-metadata.c grl-fake-metadata.h
-
-libdir=$(GRL_PLUGINS_DIR)
-fakemetadataxmldir = $(GRL_PLUGINS_CONF_DIR)
-fakemetadataxml_DATA = $(FAKEMETADATA_PLUGIN_ID).xml
-
-EXTRA_DIST = $(fakemetadataxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/fake-metadata/grl-fake-metadata.c b/src/fake-metadata/grl-fake-metadata.c
deleted file mode 100644
index 463b284..0000000
--- a/src/fake-metadata/grl-fake-metadata.c
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
-
-#include "grl-fake-metadata.h"
-
-#define GRL_LOG_DOMAIN_DEFAULT fake_metadata_log_domain
-GRL_LOG_DOMAIN_STATIC(fake_metadata_log_domain);
-
-#define PLUGIN_ID FAKEMETADATA_PLUGIN_ID
-
-#define SOURCE_ID "grl-fake-metadata"
-#define SOURCE_NAME "Fake Metadata Provider"
-#define SOURCE_DESC "A source for faking metadata resolution"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-
-static GrlFakeMetadataSource *grl_fake_metadata_source_new (void);
-
-static void grl_fake_metadata_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs);
-
-static void grl_fake_metadata_source_set_metadata (GrlMetadataSource *source,
- GrlMetadataSourceSetMetadataSpec *sms);
-
-static const GList *grl_fake_metadata_source_supported_keys (GrlMetadataSource *source);
-
-static const GList *grl_fake_metadata_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id);
-
-static const GList *grl_fake_metadata_source_writable_keys (GrlMetadataSource *source);
-
-gboolean grl_fake_metadata_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-
-/* =================== GrlFakeMetadata Plugin =============== */
-
-gboolean
-grl_fake_metadata_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (fake_metadata_log_domain, "fake-metadata");
-
- GRL_DEBUG ("grl_fake_metadata_source_plugin_init");
-
- GrlFakeMetadataSource *source = grl_fake_metadata_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_fake_metadata_source_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== GrlFakeMetadata GObject ================ */
-
-static GrlFakeMetadataSource *
-grl_fake_metadata_source_new (void)
-{
- GRL_DEBUG ("grl_fake_metadata_source_new");
- return g_object_new (GRL_FAKE_METADATA_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_fake_metadata_source_class_init (GrlFakeMetadataSourceClass * klass)
-{
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- metadata_class->supported_keys = grl_fake_metadata_source_supported_keys;
- metadata_class->key_depends = grl_fake_metadata_source_key_depends;
- metadata_class->resolve = grl_fake_metadata_source_resolve;
- metadata_class->set_metadata = grl_fake_metadata_source_set_metadata;
- metadata_class->writable_keys = grl_fake_metadata_source_writable_keys;
-}
-
-static void
-grl_fake_metadata_source_init (GrlFakeMetadataSource *source)
-{
-}
-
-G_DEFINE_TYPE (GrlFakeMetadataSource,
- grl_fake_metadata_source,
- GRL_TYPE_METADATA_SOURCE);
-
-/* ======================= Utilities ==================== */
-
-static void
-fill_metadata (GrlMedia *media, GrlKeyID key_id)
-{
- if (key_id == GRL_METADATA_KEY_AUTHOR) {
- grl_media_set_author (media, "fake author");
- } else if (key_id == GRL_METADATA_KEY_ARTIST) {
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_ARTIST, "fake artist");
- } else if (key_id == GRL_METADATA_KEY_ALBUM) {
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_ALBUM, "fake album");
- } else if (key_id == GRL_METADATA_KEY_GENRE) {
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_GENRE, "fake genre");
- } else if (key_id == GRL_METADATA_KEY_DESCRIPTION) {
- grl_media_set_description (media, "fake description");
- } else if (key_id == GRL_METADATA_KEY_DURATION) {
- grl_media_set_duration (media, 99);
- } else if (key_id == GRL_METADATA_KEY_DATE) {
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_DATE, "01/01/1970");
- } else if (key_id == GRL_METADATA_KEY_THUMBNAIL) {
- grl_media_set_thumbnail (media,
- "http://fake.thumbnail.com/fake-image.jpg");
- }
-}
-
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_fake_metadata_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_AUTHOR,
- GRL_METADATA_KEY_ARTIST,
- GRL_METADATA_KEY_ALBUM,
- GRL_METADATA_KEY_GENRE,
- GRL_METADATA_KEY_DESCRIPTION,
- GRL_METADATA_KEY_DURATION,
- GRL_METADATA_KEY_DATE,
- GRL_METADATA_KEY_THUMBNAIL,
- NULL);
- }
- return keys;
-}
-
-static const GList *
-grl_fake_metadata_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id)
-{
- static GList *deps = NULL;
- if (!deps) {
- deps = grl_metadata_key_list_new (GRL_METADATA_KEY_TITLE, NULL);
- }
-
- if (key_id == GRL_METADATA_KEY_AUTHOR ||
- key_id == GRL_METADATA_KEY_ARTIST ||
- key_id == GRL_METADATA_KEY_ALBUM ||
- key_id == GRL_METADATA_KEY_GENRE ||
- key_id == GRL_METADATA_KEY_DESCRIPTION ||
- key_id == GRL_METADATA_KEY_DURATION ||
- key_id == GRL_METADATA_KEY_DATE ||
- key_id == GRL_METADATA_KEY_THUMBNAIL) {
- return deps;
- }
-
- return NULL;
-}
-
-static const GList *
-grl_fake_metadata_source_writable_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ALBUM,
- GRL_METADATA_KEY_ARTIST,
- GRL_METADATA_KEY_GENRE,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_fake_metadata_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs)
-{
- GRL_DEBUG ("grl_fake_metadata_source_resolve");
-
- GList *iter;
-
- iter = rs->keys;
- while (iter) {
- fill_metadata (GRL_MEDIA (rs->media), iter->data);
- iter = g_list_next (iter);
- }
-
- rs->callback (source, rs->media, rs->user_data, NULL);
-}
-
-static void
-grl_fake_metadata_source_set_metadata (GrlMetadataSource *source,
- GrlMetadataSourceSetMetadataSpec *sms)
-{
- GRL_DEBUG ("grl_fake_metadata_source_set_metadata");
- GRL_DEBUG (" Faking set metadata for %d keys!", g_list_length (sms->keys));
- sms->callback (sms->source, sms->media, NULL, sms->user_data, NULL);
-}
diff --git a/src/fake-metadata/grl-fake-metadata.h b/src/fake-metadata/grl-fake-metadata.h
deleted file mode 100644
index 5d80453..0000000
--- a/src/fake-metadata/grl-fake-metadata.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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_FAKE_METADATA_SOURCE_H_
-#define _GRL_FAKE_METADATA_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_FAKE_METADATA_SOURCE_TYPE \
- (grl_fake_metadata_source_get_type ())
-
-#define GRL_FAKE_METADATA_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_FAKE_METADATA_SOURCE_TYPE, \
- GrlFakeMetadataSource))
-
-#define GRL_IS_FAKE_METADATA_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_FAKE_METADATA_SOURCE_TYPE))
-
-#define GRL_FAKE_METADATA_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_FAKE_METADATA_SOURCE_TYPE, \
- GrlFakeMetadataSourceClass))
-
-#define GRL_IS_FAKE_METADATA_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_FAKE_METADATA_SOURCE_TYPE))
-
-#define GRL_FAKE_METADATA_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_FAKE_METADATA_SOURCE_TYPE, \
- GrlFakeMetadataSourceClass))
-
-typedef struct _GrlFakeMetadataSource GrlFakeMetadataSource;
-
-struct _GrlFakeMetadataSource {
-
- GrlMetadataSource parent;
-
-};
-
-typedef struct _GrlFakeMetadataSourceClass GrlFakeMetadataSourceClass;
-
-struct _GrlFakeMetadataSourceClass {
-
- GrlMetadataSourceClass parent_class;
-
-};
-
-GType grl_fake_metadata_source_get_type (void);
-
-#endif /* _GRL_FAKE_METADATA_SOURCE_H_ */
diff --git a/src/fake-metadata/grl-fake-metadata.xml b/src/fake-metadata/grl-fake-metadata.xml
deleted file mode 100644
index 11e4d32..0000000
--- a/src/fake-metadata/grl-fake-metadata.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Fake Metadata Provider</name>
- <description>A plugin for faking metadata resolution</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/filesystem/Makefile.am b/src/filesystem/Makefile.am
deleted file mode 100644
index c685b85..0000000
--- a/src/filesystem/Makefile.am
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Iago Toral Quiroga <itoral igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlfilesystem.la
-
-libgrlfilesystem_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GIO_CFLAGS)
-
-libgrlfilesystem_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GIO_LIBS)
-
-libgrlfilesystem_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlfilesystem_la_SOURCES = grl-filesystem.c grl-filesystem.h
-
-libdir=$(GRL_PLUGINS_DIR)
-filesystemxmldir = $(GRL_PLUGINS_CONF_DIR)
-filesystemxml_DATA = $(FILESYSTEM_PLUGIN_ID).xml
-
-EXTRA_DIST = $(filesystemxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/filesystem/TODO b/src/filesystem/TODO
deleted file mode 100644
index fb6c8ed..0000000
--- a/src/filesystem/TODO
+++ /dev/null
@@ -1,2 +0,0 @@
-- childcount for categories does not filter media-only files
- - maybe this is acceptable (since this way it has a performance benefot)
diff --git a/src/filesystem/grl-filesystem.c b/src/filesystem/grl-filesystem.c
deleted file mode 100644
index d4fe60c..0000000
--- a/src/filesystem/grl-filesystem.c
+++ /dev/null
@@ -1,1343 +0,0 @@
-/*
- * Copyright (C) 2010, 2011 Igalia S.L.
- * Copyright (C) 2011 Intel Corporation.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
-#include <gio/gio.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "grl-filesystem.h"
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT filesystem_log_domain
-GRL_LOG_DOMAIN_STATIC(filesystem_log_domain);
-
-/* -------- File info ------- */
-
-#define FILE_ATTRIBUTES \
- G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
- G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
- G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
- G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
- G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
- G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," \
- G_FILE_ATTRIBUTE_THUMBNAILING_FAILED
-
-#define FILE_ATTRIBUTES_FAST \
- G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN
-
-/* ---- Emission chunks ----- */
-
-#define BROWSE_IDLE_CHUNK_SIZE 5
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID FILESYSTEM_PLUGIN_ID
-
-#define SOURCE_ID "grl-filesystem"
-#define SOURCE_NAME "Filesystem"
-#define SOURCE_DESC "A source for browsing the filesystem"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-/* --- Grilo Filesystem Private --- */
-
-#define GRL_FILESYSTEM_SOURCE_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_FILESYSTEM_SOURCE_TYPE, \
- GrlFilesystemSourcePrivate))
-
-struct _GrlFilesystemSourcePrivate {
- GList *chosen_paths;
- guint max_search_depth;
- /* a mapping operation_id -> GCancellable to cancel this operation */
- GHashTable *cancellables;
- /* Monitors for changes in directories */
- GList *monitors;
- GCancellable *cancellable_monitors;
-};
-
-/* --- Data types --- */
-
-typedef struct _RecursiveOperation RecursiveOperation;
-
-typedef gboolean (*RecursiveOperationCb) (GFileInfo *file_info,
- RecursiveOperation *operation);
-
-typedef struct {
- GrlMediaSourceBrowseSpec *spec;
- GList *entries;
- GList *current;
- const gchar *path;
- guint remaining;
- GCancellable *cancellable;
- guint id;
-} BrowseIdleData;
-
-struct _RecursiveOperation {
- RecursiveOperationCb on_cancel;
- RecursiveOperationCb on_finish;
- RecursiveOperationCb on_dir;
- RecursiveOperationCb on_file;
- gpointer on_dir_data;
- gpointer on_file_data;
- GCancellable *cancellable;
- GQueue *directories;
- guint max_depth;
-};
-
-typedef struct {
- guint depth;
- GFile *directory;
-} RecursiveEntry;
-
-
-static GrlFilesystemSource *grl_filesystem_source_new (void);
-
-static void grl_filesystem_source_finalize (GObject *object);
-
-gboolean grl_filesystem_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-static const GList *grl_filesystem_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_filesystem_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms);
-
-static void grl_filesystem_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-
-static void grl_filesystem_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-
-
-static gboolean grl_filesystem_test_media_from_uri (GrlMediaSource *source,
- const gchar *uri);
-
-static void grl_filesystem_get_media_from_uri (GrlMediaSource *source,
- GrlMediaSourceMediaFromUriSpec *mfus);
-
-static void grl_filesystem_source_cancel (GrlMediaSource *source,
- guint operation_id);
-
-static gboolean grl_filesystem_source_notify_change_start (GrlMediaSource *source,
- GError **error);
-
-static gboolean grl_filesystem_source_notify_change_stop (GrlMediaSource *source,
- GError **error);
-
-/* =================== Filesystem Plugin =============== */
-
-gboolean
-grl_filesystem_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GrlConfig *config;
- GList *chosen_paths = NULL;
- guint max_search_depth = GRILO_CONF_MAX_SEARCH_DEPTH_DEFAULT;
-
- GRL_LOG_DOMAIN_INIT (filesystem_log_domain, "filesystem");
-
- GRL_DEBUG ("filesystem_plugin_init");
-
- GrlFilesystemSource *source = grl_filesystem_source_new ();
-
- for (; configs; configs = g_list_next (configs)) {
- gchar *path;
- config = GRL_CONFIG (configs->data);
- path = grl_config_get_string (config, GRILO_CONF_CHOSEN_PATH);
- if (path) {
- chosen_paths = g_list_append (chosen_paths, path);
- }
- if (grl_config_has_param (config, GRILO_CONF_MAX_SEARCH_DEPTH)) {
- max_search_depth = (guint)grl_config_get_int (config, GRILO_CONF_MAX_SEARCH_DEPTH);
- }
- }
- source->priv->chosen_paths = chosen_paths;
- source->priv->max_search_depth = max_search_depth;
-
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
-
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_filesystem_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Filesystem GObject ================ */
-
-
-G_DEFINE_TYPE (GrlFilesystemSource,
- grl_filesystem_source,
- GRL_TYPE_MEDIA_SOURCE);
-
-static GrlFilesystemSource *
-grl_filesystem_source_new (void)
-{
- GRL_DEBUG ("grl_filesystem_source_new");
- return g_object_new (GRL_FILESYSTEM_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_filesystem_source_class_init (GrlFilesystemSourceClass * klass)
-{
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- source_class->browse = grl_filesystem_source_browse;
- source_class->search = grl_filesystem_source_search;
- source_class->cancel = grl_filesystem_source_cancel;
- source_class->notify_change_start = grl_filesystem_source_notify_change_start;
- source_class->notify_change_stop = grl_filesystem_source_notify_change_stop;
- source_class->metadata = grl_filesystem_source_metadata;
- source_class->test_media_from_uri = grl_filesystem_test_media_from_uri;
- source_class->media_from_uri = grl_filesystem_get_media_from_uri;
- G_OBJECT_CLASS (source_class)->finalize = grl_filesystem_source_finalize;
- metadata_class->supported_keys = grl_filesystem_source_supported_keys;
- g_type_class_add_private (klass, sizeof (GrlFilesystemSourcePrivate));
-}
-
-static void
-grl_filesystem_source_init (GrlFilesystemSource *source)
-{
- source->priv = GRL_FILESYSTEM_SOURCE_GET_PRIVATE (source);
- source->priv->cancellables = g_hash_table_new (NULL, NULL);
-}
-
-static void
-grl_filesystem_source_finalize (GObject *object)
-{
- GrlFilesystemSource *filesystem_source = GRL_FILESYSTEM_SOURCE (object);
- g_list_foreach (filesystem_source->priv->chosen_paths, (GFunc) g_free, NULL);
- g_list_free (filesystem_source->priv->chosen_paths);
- g_hash_table_unref (filesystem_source->priv->cancellables);
- G_OBJECT_CLASS (grl_filesystem_source_parent_class)->finalize (object);
-}
-
-/* ======================= Utilities ==================== */
-
-static void recursive_operation_next_entry (RecursiveOperation *operation);
-static void add_monitor (GrlFilesystemSource *fs_source, GFile *dir);
-static void cancel_monitors (GrlFilesystemSource *fs_source);
-
-static gboolean
-mime_is_video (const gchar *mime)
-{
- return strstr (mime, "video") != NULL;
-}
-
-static gboolean
-mime_is_audio (const gchar *mime)
-{
- return strstr (mime, "audio") != NULL;
-}
-
-static gboolean
-mime_is_image (const gchar *mime)
-{
- return strstr (mime, "image") != NULL;
-}
-
-static gboolean
-mime_is_media (const gchar *mime)
-{
- if (!mime)
- return FALSE;
- if (!strcmp (mime, "inode/directory"))
- return TRUE;
- if (mime_is_audio (mime))
- return TRUE;
- if (mime_is_video (mime))
- return TRUE;
- if (mime_is_image (mime))
- return TRUE;
- return FALSE;
-}
-
-static gboolean
-file_is_valid_content (const gchar *path, gboolean fast)
-{
- const gchar *mime;
- GError *error = NULL;
- gboolean is_media;
- GFile *file;
- GFileInfo *info;
- GFileType type;
- const gchar *spec;
-
- if (fast) {
- spec = FILE_ATTRIBUTES_FAST;
- } else {
- spec = FILE_ATTRIBUTES;
- }
-
- file = g_file_new_for_path (path);
- info = g_file_query_info (file, spec, 0, NULL, &error);
- if (error) {
- GRL_WARNING ("Failed to get attributes for file '%s': %s",
- path, error->message);
- g_error_free (error);
- g_object_unref (file);
- return FALSE;
- } else {
- if (g_file_info_get_is_hidden (info)) {
- is_media = FALSE;
- } else {
- if (fast) {
- /* In fast mode we do not check mime-types,
- any non-hidden file is accepted */
- is_media = TRUE;
- } else {
- type = g_file_info_get_file_type (info);
- mime = g_file_info_get_content_type (info);
- if (type == G_FILE_TYPE_DIRECTORY || mime_is_media (mime)) {
- is_media = TRUE;
- } else {
- is_media = FALSE;
- }
- }
- }
- g_object_unref (info);
- g_object_unref (file);
- return is_media;
- }
-}
-
-static void
-set_container_childcount (const gchar *path,
- GrlMedia *media,
- gboolean fast)
-{
- GDir *dir;
- GError *error = NULL;
- gint count;
- const gchar *entry_name;
-
- /* Open directory */
- GRL_DEBUG ("Opening directory '%s' for childcount", path);
- dir = g_dir_open (path, 0, &error);
- if (error) {
- GRL_WARNING ("Failed to open directory '%s': %s", path, error->message);
- g_error_free (error);
- return;
- }
-
- /* Count valid entries */
- count = 0;
- while ((entry_name = g_dir_read_name (dir)) != NULL) {
- gchar *entry_path;
- if (strcmp (path, G_DIR_SEPARATOR_S)) {
- entry_path = g_strconcat (path, G_DIR_SEPARATOR_S, entry_name, NULL);
- } else {
- entry_path = g_strconcat (path, entry_name, NULL);
- }
- if (file_is_valid_content (entry_path, fast)) {
- if (fast) {
- /* in fast mode we don't compute mime-types because it is slow,
- so we can only check if the directory is totally empty (no subdirs,
- and no files), otherwise we just say we do not know the actual
- childcount */
- count = GRL_METADATA_KEY_CHILDCOUNT_UNKNOWN;
- break;
- }
- count++;
- }
- g_free (entry_path);
- }
-
- g_dir_close (dir);
-
- grl_media_box_set_childcount (GRL_MEDIA_BOX (media), count);
-}
-
-static GrlMedia *
-create_content (GrlMedia *content,
- const gchar *path,
- gboolean only_fast,
- gboolean root_dir)
-{
- GrlMedia *media = NULL;
- gchar *str;
- const gchar *mime;
- GError *error = NULL;
-
- GFile *file = g_file_new_for_path (path);
- GFileInfo *info = g_file_query_info (file,
- FILE_ATTRIBUTES,
- 0,
- NULL,
- &error);
-
- /* Update mode */
- if (content) {
- media = content;
- }
-
- if (error) {
- GRL_WARNING ("Failed to get info for file '%s': %s", path,
- error->message);
- if (!media) {
- media = grl_media_new ();
- }
-
- /* Title */
- str = g_strrstr (path, G_DIR_SEPARATOR_S);
- if (!str) {
- str = (gchar *) path;
- }
- grl_media_set_title (media, str);
- g_error_free (error);
- } else {
- mime = g_file_info_get_content_type (info);
-
- if (!media) {
- if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
- media = GRL_MEDIA (grl_media_box_new ());
- } else {
- if (mime_is_video (mime)) {
- media = grl_media_video_new ();
- } else if (mime_is_audio (mime)) {
- media = grl_media_audio_new ();
- } else if (mime_is_image (mime)) {
- media = grl_media_image_new ();
- } else {
- media = grl_media_new ();
- }
- }
- }
-
- if (!GRL_IS_MEDIA_BOX (media)) {
- grl_media_set_mime (media, mime);
- }
-
- /* Title */
- str = (gchar *) g_file_info_get_display_name (info);
- grl_media_set_title (media, str);
-
- /* Date */
- GTimeVal time;
- gchar *time_str;
- g_file_info_get_modification_time (info, &time);
- time_str = g_time_val_to_iso8601 (&time);
- grl_media_set_date (media, time_str);
- g_free (time_str);
-
- /* Thumbnail */
- gboolean thumb_failed =
- g_file_info_get_attribute_boolean (info,
- G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
- if (!thumb_failed) {
- const gchar *thumb =
- g_file_info_get_attribute_byte_string (info,
- G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
- if (thumb) {
- gchar *thumb_uri = g_filename_to_uri (thumb, NULL, NULL);
- if (thumb_uri) {
- grl_media_set_thumbnail (media, thumb_uri);
- g_free (thumb_uri);
- }
- }
- }
-
- g_object_unref (info);
- }
-
- grl_media_set_id (media, root_dir ? NULL : path);
-
- /* URL */
- str = g_strconcat ("file://", path, NULL);
- grl_media_set_url (media, str);
- g_free (str);
-
- /* Childcount */
- if (GRL_IS_MEDIA_BOX (media)) {
- set_container_childcount (path, media, only_fast);
- }
-
- g_object_unref (file);
-
- return media;
-}
-
-static gboolean
-browse_emit_idle (gpointer user_data)
-{
- BrowseIdleData *idle_data;
- guint count;
- GrlFilesystemSource *fs_source;
-
- GRL_DEBUG ("browse_emit_idle");
-
- idle_data = (BrowseIdleData *) user_data;
- fs_source = GRL_FILESYSTEM_SOURCE (idle_data->spec->source);
-
- if (g_cancellable_is_cancelled (idle_data->cancellable)) {
- GRL_DEBUG ("Browse operation %d (\"%s\") has been cancelled",
- idle_data->id, idle_data->path);
- idle_data->spec->callback(idle_data->spec->source,
- idle_data->id, NULL, 0,
- idle_data->spec->user_data, NULL);
- goto finish;
- }
-
- count = 0;
- do {
- gchar *entry_path;
- GrlMedia *content;
-
- entry_path = (gchar *) idle_data->current->data;
- content = create_content (NULL,
- entry_path,
- idle_data->spec->flags & GRL_RESOLVE_FAST_ONLY,
- FALSE);
- g_free (idle_data->current->data);
-
- idle_data->spec->callback (idle_data->spec->source,
- idle_data->spec->browse_id,
- content,
- idle_data->remaining--,
- idle_data->spec->user_data,
- NULL);
-
- idle_data->current = g_list_next (idle_data->current);
- count++;
- } while (count < BROWSE_IDLE_CHUNK_SIZE && idle_data->current);
-
- if (!idle_data->current)
- goto finish;
-
- return TRUE;
-
-finish:
- g_list_free (idle_data->entries);
- g_hash_table_remove (fs_source->priv->cancellables,
- GUINT_TO_POINTER (idle_data->id));
- g_object_unref (idle_data->cancellable);
- g_slice_free (BrowseIdleData, idle_data);
- return FALSE;
-}
-
-static void
-produce_from_path (GrlMediaSourceBrowseSpec *bs, const gchar *path)
-{
- GDir *dir;
- GError *error = NULL;
- const gchar *entry;
- guint skip, count;
- GList *entries = NULL;
- GList *iter;
-
- /* Open directory */
- GRL_DEBUG ("Opening directory '%s'", path);
- dir = g_dir_open (path, 0, &error);
- if (error) {
- GRL_WARNING ("Failed to open directory '%s': %s", path, error->message);
- bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, error);
- g_error_free (error);
- return;
- }
-
- /* Filter out media and directories */
- while ((entry = g_dir_read_name (dir)) != NULL) {
- gchar *file;
- if (strcmp (path, G_DIR_SEPARATOR_S)) {
- file = g_strconcat (path, G_DIR_SEPARATOR_S, entry, NULL);
- } else {
- file = g_strconcat (path, entry, NULL);
- }
- if (file_is_valid_content (file, FALSE)) {
- entries = g_list_prepend (entries, file);
- }
- }
-
- /* Apply skip and count */
- skip = bs->skip;
- count = bs->count;
- iter = entries;
- while (iter) {
- gboolean remove;
- GList *tmp;
- if (skip > 0) {
- skip--;
- remove = TRUE;
- } else if (count > 0) {
- count--;
- remove = FALSE;
- } else {
- remove = TRUE;
- }
- if (remove) {
- tmp = iter;
- iter = g_list_next (iter);
- g_free (tmp->data);
- entries = g_list_delete_link (entries, tmp);
- } else {
- iter = g_list_next (iter);
- }
- }
-
- /* Emit results */
- if (entries) {
- /* Use the idle loop to avoid blocking for too long */
- BrowseIdleData *idle_data = g_slice_new (BrowseIdleData);
- idle_data->spec = bs;
- idle_data->remaining = bs->count - count - 1;
- idle_data->path = path;
- idle_data->entries = entries;
- idle_data->current = entries;
- idle_data->cancellable = g_cancellable_new ();
- idle_data->id = bs->browse_id;
- g_hash_table_insert (GRL_FILESYSTEM_SOURCE (bs->source)->priv->cancellables,
- GUINT_TO_POINTER (bs->browse_id),
- idle_data->cancellable);
-
- g_idle_add (browse_emit_idle, idle_data);
- } else {
- /* No results */
- bs->callback (bs->source,
- bs->browse_id,
- NULL,
- 0,
- bs->user_data,
- NULL);
- }
-
- g_dir_close (dir);
-}
-
-static RecursiveEntry *
-recursive_entry_new (guint depth, GFile *directory)
-{
- RecursiveEntry *entry;
-
- entry = g_slice_new(RecursiveEntry);
- entry->depth = depth;
- entry->directory = g_object_ref (directory);
-
- return entry;
-}
-
-static void
-recursive_entry_free (RecursiveEntry *entry)
-{
- g_object_unref (entry->directory);
- g_slice_free (RecursiveEntry, entry);
-}
-
-static RecursiveOperation *
-recursive_operation_new ()
-{
- RecursiveOperation *operation;
-
- operation = g_slice_new0 (RecursiveOperation);
- operation->directories = g_queue_new ();
- operation->cancellable = g_cancellable_new ();
-
- return operation;
-}
-
-static void
-recursive_operation_free (RecursiveOperation *operation)
-{
- g_queue_foreach (operation->directories, (GFunc) recursive_entry_free, NULL);
- g_queue_free (operation->directories);
- g_object_unref (operation->cancellable);
- g_slice_free (RecursiveOperation, operation);
-}
-
-static void
-recursive_operation_got_file (GFileEnumerator *enumerator, GAsyncResult *res, RecursiveOperation *operation)
-{
- GList *files;
- GError *error = NULL;
- gboolean continue_operation = TRUE;
-
- GRL_DEBUG (__func__);
-
- files = g_file_enumerator_next_files_finish (enumerator, res, &error);
- if (error) {
- if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
- GRL_WARNING ("Got error: %s", error->message);
- g_error_free (error);
- goto finished;
- }
-
- if (files) {
- GFileInfo *file_info;
- RecursiveEntry *entry;
-
- /* we assume there is only one GFileInfo in the list since that's what we ask
- * for when calling g_file_enumerator_next_files_async() */
- file_info = (GFileInfo *)files->data;
- g_list_free (files);
- /* Get the entry we are running now */
- entry = g_queue_peek_head (operation->directories);
- switch (g_file_info_get_file_type (file_info)) {
- case G_FILE_TYPE_SYMBOLIC_LINK:
- /* we're too afraid of infinite recursion to touch this for now */
- break;
- case G_FILE_TYPE_DIRECTORY:
- {
- if (entry->depth < operation->max_depth) {
- GFile *subdir;
- RecursiveEntry *subentry;
-
- if (operation->on_dir) {
- continue_operation = operation->on_dir(file_info, operation);
- }
-
- if (continue_operation) {
- subdir = g_file_get_child (entry->directory,
- g_file_info_get_name (file_info));
- subentry = recursive_entry_new (entry->depth + 1, subdir);
- g_queue_push_tail (operation->directories, subentry);
- g_object_unref (subdir);
- } else {
- g_object_unref (file_info);
- goto finished;
- }
- }
- }
- break;
- case G_FILE_TYPE_REGULAR:
- if (operation->on_file) {
- continue_operation = operation->on_file(file_info, operation);
- if (!continue_operation) {
- g_object_unref (file_info);
- goto finished;
- }
- }
- break;
- default:
- /* this file is a weirdo, we ignore it */
- break;
- }
- g_object_unref (file_info);
- } else { /* end of enumerator */
- goto finished;
- }
-
- g_file_enumerator_next_files_async (enumerator, 1, G_PRIORITY_DEFAULT,
- operation->cancellable,
- (GAsyncReadyCallback)recursive_operation_got_file,
- operation);
-
- return;
-
-finished:
- /* we're done with this dir/enumerator, let's treat the next one */
- g_object_unref (enumerator);
- recursive_entry_free (g_queue_pop_head (operation->directories));
- if (continue_operation) {
- recursive_operation_next_entry (operation);
- } else {
- recursive_operation_free (operation);
- }
-}
-
-static void
-recursive_operation_got_entry (GFile *directory, GAsyncResult *res, RecursiveOperation *operation)
-{
- GError *error = NULL;
- GFileEnumerator *enumerator;
-
- GRL_DEBUG (__func__);
-
- enumerator = g_file_enumerate_children_finish (directory, res, &error);
- if (error) {
- GRL_WARNING ("Got error: %s", error->message);
- g_error_free (error);
- g_object_unref (enumerator);
- /* we couldn't get the children of this directory, but we probably have
- * other directories to try */
- recursive_entry_free (g_queue_pop_head (operation->directories));
- recursive_operation_next_entry (operation);
- return;
- }
-
- g_file_enumerator_next_files_async (enumerator, 1, G_PRIORITY_DEFAULT,
- operation->cancellable,
- (GAsyncReadyCallback)recursive_operation_got_file,
- operation);
-}
-
-static void
-recursive_operation_next_entry (RecursiveOperation *operation)
-{
- RecursiveEntry *entry;
-
- GRL_DEBUG (__func__);
-
- if (g_cancellable_is_cancelled (operation->cancellable)) {
- /* We've been cancelled! */
- GRL_DEBUG ("Operation has been cancelled");
- if (operation->on_cancel) {
- operation->on_cancel(NULL, operation);
- }
- goto finished;
- }
-
- entry = g_queue_peek_head (operation->directories);
- if (!entry) { /* We've crawled everything, before reaching count */
- if (operation->on_finish) {
- operation->on_finish (NULL, operation);
- }
- goto finished;
- }
-
- g_file_enumerate_children_async (entry->directory, G_FILE_ATTRIBUTE_STANDARD_TYPE ","
- G_FILE_ATTRIBUTE_STANDARD_NAME ","
- G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- G_PRIORITY_DEFAULT,
- operation->cancellable,
- (GAsyncReadyCallback)recursive_operation_got_entry,
- operation);
-
- return;
-
-finished:
- recursive_operation_free (operation);
-}
-
-static void
-recursive_operation_initialize (RecursiveOperation *operation, GrlFilesystemSource *source)
-{
- GList *chosen_paths, *path;
-
- chosen_paths = source->priv->chosen_paths;
- if (chosen_paths) {
- for (path = chosen_paths; path; path = g_list_next (path)) {
- GFile *directory = g_file_new_for_path (path->data);
- g_queue_push_tail (operation->directories,
- recursive_entry_new (0, directory));
- g_object_unref (directory);
- }
- } else {
- const gchar *home;
- GFile *directory;
-
- home = g_getenv ("HOME");
- directory = g_file_new_for_path (home);
- g_queue_push_tail (operation->directories,
- recursive_entry_new (0, directory));
- g_object_unref (directory);
- }
-}
-
-static gboolean
-cancel_cb (GFileInfo *file_info, RecursiveOperation *operation)
-{
- GrlFilesystemSource *fs_source;
-
- if (operation->on_file_data) {
- GrlMediaSourceSearchSpec *ss =
- (GrlMediaSourceSearchSpec *) operation->on_file_data;
- fs_source = GRL_FILESYSTEM_SOURCE (ss->source);
- g_hash_table_remove (fs_source->priv->cancellables,
- GUINT_TO_POINTER (ss->search_id));
- ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, NULL);
- }
-
- if (operation->on_dir_data) {
- /* Remove all monitors */
- fs_source = GRL_FILESYSTEM_SOURCE (operation->on_dir_data);
- cancel_monitors (fs_source);
- }
- return FALSE;
-}
-
-static gboolean
-finish_cb (GFileInfo *file_info, RecursiveOperation *operation)
-{
- if (operation->on_file_data) {
- GrlMediaSourceSearchSpec *ss =
- (GrlMediaSourceSearchSpec *) operation->on_file_data;
- g_hash_table_remove (GRL_FILESYSTEM_SOURCE (ss->source)->priv->cancellables,
- GUINT_TO_POINTER (ss->search_id));
- ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, NULL);
- }
-
- if (operation->on_dir_data) {
- GRL_FILESYSTEM_SOURCE (operation->on_dir_data)->priv->cancellable_monitors = NULL;
- }
-
- return FALSE;
-}
-
-/* return TRUE if more files need to be returned, FALSE if we sent the count */
-static gboolean
-file_cb (GFileInfo *file_info, RecursiveOperation *operation)
-{
- gchar *needle = NULL;
- gchar *haystack = NULL;
- gchar *normalized_needle = NULL;
- gchar *normalized_haystack = NULL;
- GrlMediaSourceSearchSpec *ss = operation->on_file_data;
- gint remaining = -1;
-
- GRL_DEBUG (__func__);
-
- if (ss == NULL)
- return FALSE;
-
- if (ss->text) {
- haystack = g_utf8_casefold (g_file_info_get_display_name (file_info), -1);
- normalized_haystack = g_utf8_normalize (haystack, -1, G_NORMALIZE_ALL);
-
- needle = g_utf8_casefold (ss->text, -1);
- normalized_needle = g_utf8_normalize (needle, -1, G_NORMALIZE_ALL);
- }
-
- if (!ss->text ||
- strstr (normalized_haystack, normalized_needle)) {
- GrlMedia *media = NULL;
- RecursiveEntry *entry;
- GFile *file;
- gchar *path;
-
- entry = g_queue_peek_head (operation->directories);
- file = g_file_get_child (entry->directory,
- g_file_info_get_name (file_info));
- path = g_file_get_path (file);
-
- /* FIXME: both file_is_valid_content() and create_content() are likely to block */
- if (file_is_valid_content (path, FALSE)) {
- if (ss->skip) {
- ss->skip--;
- } else {
- media = create_content (NULL, path, ss->flags & GRL_RESOLVE_FAST_ONLY, FALSE);
- }
- }
-
- g_free (path);
- g_object_unref (file);
-
- if (media) {
- ss->count--;
- if (ss->count == 0) {
- remaining = 0;
- }
- ss->callback (ss->source, ss->search_id, media, remaining, ss->user_data, NULL);
- }
- }
-
- g_free (haystack);
- g_free (normalized_haystack);
- g_free (needle);
- g_free (normalized_needle);
- return remaining == -1;
-}
-
-static void
-notify_parent_change (GrlMediaSource *source, GFile *child, GrlMediaSourceChangeType change)
-{
- GFile *parent;
- GrlMedia *media;
- gchar *parent_path;
-
- parent = g_file_get_parent (child);
- if (parent) {
- parent_path = g_file_get_path (parent);
- } else {
- parent_path = g_strdup ("/");
- }
-
- media = create_content (NULL, parent_path, GRL_RESOLVE_FAST_ONLY, parent == NULL);
- grl_media_source_notify_change (source, media, change, FALSE);
- g_object_unref (media);
-
- if (parent) {
- g_object_unref (parent);
- }
- g_free (parent_path);
-}
-
-static void
-directory_changed (GFileMonitor *monitor,
- GFile *file,
- GFile *other_file,
- GFileMonitorEvent event,
- gpointer data)
-{
- GrlMediaSource *source = GRL_MEDIA_SOURCE (data);
- gchar *file_path, *other_file_path;
- gchar *file_parent_path = NULL;
- gchar *other_file_parent_path = NULL;
- GFile *file_parent, *other_file_parent;
- GFileInfo *file_info;
-
- if (event == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT ||
- event == G_FILE_MONITOR_EVENT_CREATED) {
- file_path = g_file_get_path (file);
- if (file_is_valid_content (file_path, TRUE)) {
- notify_parent_change (source,
- file,
- (event == G_FILE_MONITOR_EVENT_CREATED)? GRL_CONTENT_ADDED: GRL_CONTENT_CHANGED);
- if (event == G_FILE_MONITOR_EVENT_CREATED) {
- file_info = g_file_query_info (file,
- G_FILE_ATTRIBUTE_STANDARD_TYPE,
- G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
- NULL,
- NULL);
- if (file_info) {
- if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) {
- add_monitor (GRL_FILESYSTEM_SOURCE (source), file);
- }
- g_object_unref (file_info);
- }
- }
- }
- g_free (file_path);
- } else if (event == G_FILE_MONITOR_EVENT_DELETED) {
- notify_parent_change (source, file, GRL_CONTENT_REMOVED);
- } else if (event == G_FILE_MONITOR_EVENT_MOVED) {
- other_file_path = g_file_get_path (other_file);
- if (file_is_valid_content (other_file_path, TRUE)) {
- file_parent = g_file_get_parent (file);
- if (file_parent) {
- file_parent_path = g_file_get_path (file_parent);
- g_object_unref (file_parent);
- } else {
- file_parent_path = NULL;
- }
- other_file_parent = g_file_get_parent (other_file);
- if (other_file_parent) {
- other_file_parent_path = g_file_get_path (other_file_parent);
- g_object_unref (other_file_parent);
- } else {
- other_file_parent_path = NULL;
- }
-
- if (g_strcmp0 (file_parent_path, other_file_parent_path) == 0) {
- notify_parent_change (source, file, GRL_CONTENT_CHANGED);
- } else {
- notify_parent_change (source, file, GRL_CONTENT_REMOVED);
- notify_parent_change (source, other_file, GRL_CONTENT_ADDED);
- }
- }
- g_free (file_parent_path);
- g_free (other_file_parent_path);
- }
-}
-
-static void
-cancel_monitors (GrlFilesystemSource *fs_source)
-{
- g_list_foreach (fs_source->priv->monitors,
- (GFunc) g_file_monitor_cancel,
- NULL);
- g_list_foreach (fs_source->priv->monitors,
- (GFunc) g_object_unref,
- NULL);
- g_list_free (fs_source->priv->monitors);
- fs_source->priv->monitors = NULL;
-}
-
-static void
-add_monitor (GrlFilesystemSource *fs_source, GFile *dir)
-{
- GFileMonitor *monitor;
-
- monitor = g_file_monitor_directory (dir, G_FILE_MONITOR_SEND_MOVED, NULL, NULL);
- if (monitor) {
- fs_source->priv->monitors = g_list_prepend (fs_source->priv->monitors,
- monitor);
- g_signal_connect (monitor,
- "changed",
- G_CALLBACK (directory_changed),
- fs_source);
- } else {
- GRL_DEBUG ("Unable to set up monitor in %s\n", g_file_get_path (dir));
- }
-}
-
-static gboolean
-directory_cb (GFileInfo *dir_info, RecursiveOperation *operation)
-{
- RecursiveEntry *entry;
- GFile *dir;
- GrlFilesystemSource *fs_source;
-
- fs_source = GRL_FILESYSTEM_SOURCE (operation->on_dir_data);
- entry = g_queue_peek_head (operation->directories);
- dir = g_file_get_child (entry->directory,
- g_file_info_get_name (dir_info));
-
- add_monitor (fs_source, dir);
- g_object_unref (dir);
-
- return TRUE;
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_filesystem_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_URL,
- GRL_METADATA_KEY_MIME,
- GRL_METADATA_KEY_DATE,
- GRL_METADATA_KEY_CHILDCOUNT,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_filesystem_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- const gchar *id;
- GList *chosen_paths;
-
- GRL_DEBUG ("grl_filesystem_source_browse");
-
- id = grl_media_get_id (bs->container);
- chosen_paths = GRL_FILESYSTEM_SOURCE(source)->priv->chosen_paths;
- if (!id && chosen_paths) {
- guint remaining = g_list_length (chosen_paths);
-
- if (remaining == 1) {
- produce_from_path (bs, chosen_paths->data);
- } else {
- for (; chosen_paths; chosen_paths = g_list_next (chosen_paths)) {
- GrlMedia *content = create_content (NULL,
- (gchar *) chosen_paths->data,
- GRL_RESOLVE_FAST_ONLY,
- FALSE);
-
- bs->callback (source,
- bs->browse_id,
- content,
- --remaining,
- bs->user_data,
- NULL);
- }
- }
- } else {
- produce_from_path (bs, id ? id : G_DIR_SEPARATOR_S);
- }
-}
-
-static void grl_filesystem_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss)
-{
- RecursiveOperation *operation;
- GrlFilesystemSource *fs_source;
-
- GRL_DEBUG ("grl_filesystem_source_search");
-
- fs_source = GRL_FILESYSTEM_SOURCE (source);
-
- operation = recursive_operation_new ();
- operation->on_cancel = cancel_cb;
- operation->on_finish = finish_cb;
- operation->on_file = file_cb;
- operation->on_file_data = ss;
- operation->max_depth = fs_source->priv->max_search_depth;
- g_hash_table_insert (GRL_FILESYSTEM_SOURCE (source)->priv->cancellables,
- GUINT_TO_POINTER (ss->search_id),
- operation->cancellable);
-
- recursive_operation_initialize (operation, fs_source);
- recursive_operation_next_entry (operation);
-}
-
-static void
-grl_filesystem_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- const gchar *path;
- const gchar *id;
-
- GRL_DEBUG ("grl_filesystem_source_metadata");
-
- id = grl_media_get_id (ms->media);
- path = id ? id : G_DIR_SEPARATOR_S;
-
- if (g_file_test (path, G_FILE_TEST_EXISTS)) {
- create_content (ms->media, path,
- ms->flags & GRL_RESOLVE_FAST_ONLY,
- !id);
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- } else {
- GError *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "File '%s' does not exist",
- path);
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- }
-}
-
-static gboolean
-grl_filesystem_test_media_from_uri (GrlMediaSource *source,
- const gchar *uri)
-{
- gchar *path, *scheme;
- GError *error = NULL;
- gboolean ret = FALSE;
-
- GRL_DEBUG ("grl_filesystem_test_media_from_uri");
-
- scheme = g_uri_parse_scheme (uri);
- ret = (g_strcmp0(scheme, "file") == 0);
- g_free (scheme);
- if (!ret)
- return ret;
-
- path = g_filename_from_uri (uri, NULL, &error);
- if (error != NULL) {
- g_error_free (error);
- return FALSE;
- }
-
- ret = file_is_valid_content (path, TRUE);
-
- g_free (path);
- return ret;
-}
-
-static void grl_filesystem_get_media_from_uri (GrlMediaSource *source,
- GrlMediaSourceMediaFromUriSpec *mfus)
-{
- gchar *path, *scheme;
- GError *error = NULL;
- gboolean ret = FALSE;
- GrlMedia *media;
-
- GRL_DEBUG ("grl_filesystem_get_media_from_uri");
-
- scheme = g_uri_parse_scheme (mfus->uri);
- ret = (g_strcmp0(scheme, "file") == 0);
- g_free (scheme);
- if (!ret) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED,
- "Cannot create media from '%s'", mfus->uri);
- mfus->callback (source, NULL, mfus->user_data, error);
- g_clear_error (&error);
- return;
- }
-
- path = g_filename_from_uri (mfus->uri, NULL, &error);
- if (error != NULL) {
- GError *new_error;
- new_error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED,
- "Cannot create media from '%s', error message: %s",
- mfus->uri, error->message);
- g_clear_error (&error);
- mfus->callback (source, NULL, mfus->user_data, new_error);
- g_clear_error (&new_error);
- goto beach;
- }
-
- /* FIXME: this is a blocking call, not sure we want that in here */
- /* Note: we assume create_content() never returns NULL, which seems to be true */
- media = create_content (NULL, path, mfus->flags & GRL_RESOLVE_FAST_ONLY,
- FALSE);
- mfus->callback (source, media, mfus->user_data, NULL);
-
-beach:
- g_free (path);
-}
-
-static void
-grl_filesystem_source_cancel (GrlMediaSource *source, guint operation_id)
-{
- GCancellable *cancellable;
- GrlFilesystemSourcePrivate *priv;
-
- priv = GRL_FILESYSTEM_SOURCE (source)->priv;
-
- cancellable =
- G_CANCELLABLE (g_hash_table_lookup (priv->cancellables,
- GUINT_TO_POINTER (operation_id)));
- if (cancellable)
- g_cancellable_cancel (cancellable);
-}
-
-static gboolean
-grl_filesystem_source_notify_change_start (GrlMediaSource *source,
- GError **error)
-{
- GrlFilesystemSource *fs_source;
- RecursiveOperation *operation;
-
- GRL_DEBUG (__func__);
-
- fs_source = GRL_FILESYSTEM_SOURCE (source);
- operation = recursive_operation_new ();
- operation->on_cancel = cancel_cb;
- operation->on_finish = finish_cb;
- operation->on_dir = directory_cb;
- operation->on_dir_data = fs_source;
- operation->max_depth = fs_source->priv->max_search_depth;
-
- fs_source->priv->cancellable_monitors = operation->cancellable;
-
- recursive_operation_initialize (operation, fs_source);
- recursive_operation_next_entry (operation);
-
- return TRUE;
-}
-
-static gboolean
-grl_filesystem_source_notify_change_stop (GrlMediaSource *source,
- GError **error)
-{
- GrlFilesystemSource *fs_source = GRL_FILESYSTEM_SOURCE (source);
-
- /* Check if notifying is being initialized */
- if (fs_source->priv->cancellable_monitors) {
- g_cancellable_cancel (fs_source->priv->cancellable_monitors);
- fs_source->priv->cancellable_monitors = NULL;
- } else {
- /* Cancel and remove all monitors */
- cancel_monitors (fs_source);
- }
-
- return TRUE;
-}
diff --git a/src/filesystem/grl-filesystem.h b/src/filesystem/grl-filesystem.h
deleted file mode 100644
index 428c61b..0000000
--- a/src/filesystem/grl-filesystem.h
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2010, 2011 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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_FILESYSTEM_SOURCE_H_
-#define _GRL_FILESYSTEM_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_FILESYSTEM_SOURCE_TYPE \
- (grl_filesystem_source_get_type ())
-
-#define GRL_FILESYSTEM_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_FILESYSTEM_SOURCE_TYPE, \
- GrlFilesystemSource))
-
-#define GRL_IS_FILESYSTEM_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_FILESYSTEM_SOURCE_TYPE))
-
-#define GRL_FILESYSTEM_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_FILESYSTEM_SOURCE_TYPE, \
- GrlFilesystemSourceClass))
-
-#define GRL_IS_FILESYSTEM_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass) \
- GRL_FILESYSTEM_SOURCE_TYPE))
-
-#define GRL_FILESYSTEM_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_FILESYSTEM_SOURCE_TYPE, \
- GrlFilesystemSourceClass))
-
-/* --- Grilo Configuration --- */
-#define GRILO_CONF_CHOSEN_PATH "base-path"
-#define GRILO_CONF_MAX_SEARCH_DEPTH "maximum-search-depth"
-#define GRILO_CONF_MAX_SEARCH_DEPTH_DEFAULT 6
-
-
-typedef struct _GrlFilesystemSource GrlFilesystemSource;
-typedef struct _GrlFilesystemSourcePrivate GrlFilesystemSourcePrivate;
-
-struct _GrlFilesystemSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlFilesystemSourcePrivate *priv;
-};
-
-typedef struct _GrlFilesystemSourceClass GrlFilesystemSourceClass;
-
-struct _GrlFilesystemSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_filesystem_source_get_type (void);
-
-#endif /* _GRL_FILESYSTEM_SOURCE_H_ */
diff --git a/src/filesystem/grl-filesystem.xml b/src/filesystem/grl-filesystem.xml
deleted file mode 100644
index cfb9bc6..0000000
--- a/src/filesystem/grl-filesystem.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Filesystem</name>
- <description>A plugin for browsing the filesystem</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/flickr/Makefile.am b/src/flickr/Makefile.am
deleted file mode 100644
index 66da488..0000000
--- a/src/flickr/Makefile.am
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Juan A. Suarez Romero <jasuarez igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-libplugin_LTLIBRARIES = libgrlflickr.la
-
-libgrlflickr_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(XML_CFLAGS) \
- $(GRLNET_CFLAGS)
-
-libgrlflickr_la_LIBADD = \
- $(DEPS_LIBS) \
- $(XML_LIBS) \
- $(GRLNET_LIBS)
-
-libgrlflickr_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlflickr_la_SOURCES = \
- grl-flickr.c \
- grl-flickr.h \
- gflickr.c \
- gflickr.h
-
-libplugindir = $(GRL_PLUGINS_DIR)
-flickrlibxmldir = $(GRL_PLUGINS_CONF_DIR)
-flickrlibxml_DATA = $(FLICKR_PLUGIN_ID).xml
-
-EXTRA_DIST = $(flickrlibxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/flickr/gflickr.c b/src/flickr/gflickr.c
deleted file mode 100644
index 695c7dc..0000000
--- a/src/flickr/gflickr.c
+++ /dev/null
@@ -1,1196 +0,0 @@
-#include "gflickr.h"
-#include "grl-flickr.h" /* log domain */
-
-#include <libxml/xpath.h>
-#include <gio/gio.h>
-#include <string.h>
-
-#include <grilo.h>
-#include <net/grl-net.h>
-
-
-#define G_FLICKR_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- G_FLICKR_TYPE, \
- GFlickrPrivate))
-
-#define GRL_LOG_DOMAIN_DEFAULT flickr_log_domain
-
-#define FLICKR_PHOTO_ORIG_URL \
- "http://farm%s.static.flickr.com/%s/%s_%s_o.%s"
-
-#define FLICKR_PHOTO_THUMB_URL \
- "http://farm%s.static.flickr.com/%s/%s_%s_t.jpg"
-
-#define FLICKR_PHOTO_LARGEST_URL \
- "http://farm%s.static.flickr.com/%s/%s_%s_b.jpg"
-
-#define FLICKR_ENDPOINT "http://api.flickr.com/services/rest/?"
-#define FLICKR_AUTHPOINT "http://flickr.com/services/auth/?"
-
-#define FLICKR_PHOTOS_SEARCH_METHOD "flickr.photos.search"
-#define FLICKR_PHOTOS_GETINFO_METHOD "flickr.photos.getInfo"
-#define FLICKR_PHOTOS_GETRECENT_METHOD "flickr.photos.getRecent"
-#define FLICKR_PHOTOSETS_GETLIST_METHOD "flickr.photosets.getList"
-#define FLICKR_PHOTOSETS_GETPHOTOS_METHOD "flickr.photosets.getPhotos"
-#define FLICKR_TAGS_GETHOTLIST_METHOD "flickr.tags.getHotList"
-#define FLICKR_AUTH_GETFROB_METHOD "flickr.auth.getFrob"
-#define FLICKR_AUTH_GETTOKEN_METHOD "flickr.auth.getToken"
-#define FLICKR_AUTH_CHECKTOKEN_METHOD "flickr.auth.checkToken"
-
-#define FLICKR_PHOTOS_SEARCH \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_PHOTOS_SEARCH_METHOD \
- "&user_id=%s" \
- "&extras=media,date_taken,owner_name,url_o,url_t" \
- "&per_page=%d" \
- "&page=%d" \
- "&tags=%s" \
- "&text=%s" \
- "%s"
-
-#define FLICKR_PHOTOS_GETRECENT \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_PHOTOS_GETRECENT_METHOD \
- "&extras=media,date_taken,owner_name,url_o,url_t" \
- "&per_page=%d" \
- "&page=%d" \
- "%s"
-
-#define FLICKR_PHOTOSETS_GETLIST \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_PHOTOSETS_GETLIST_METHOD \
- "%s" \
- "%s"
-
-#define FLICKR_PHOTOSETS_GETPHOTOS \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_PHOTOSETS_GETPHOTOS_METHOD \
- "&photoset_id=%s" \
- "&extras=media,date_taken,owner_name,url_o,url_t" \
- "&per_page=%d" \
- "&page=%d" \
- "%s"
-
-#define FLICKR_TAGS_GETHOTLIST \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_TAGS_GETHOTLIST_METHOD \
- "&count=%d" \
- "%s"
-
-#define FLICKR_PHOTOS_GETINFO \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_PHOTOS_GETINFO_METHOD \
- "&photo_id=%ld" \
- "%s"
-
-#define FLICKR_AUTH_GETFROB \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_AUTH_GETFROB_METHOD
-
-#define FLICKR_AUTH_GETTOKEN \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_AUTH_GETTOKEN_METHOD \
- "&frob=%s"
-
-#define FLICKR_AUTH_CHECKTOKEN \
- FLICKR_ENDPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&method=" FLICKR_AUTH_CHECKTOKEN_METHOD \
- "&auth_token=%s"
-
-#define FLICKR_AUTH_LOGINLINK \
- FLICKR_AUTHPOINT \
- "api_key=%s" \
- "&api_sig=%s" \
- "&frob=%s" \
- "&perms=%s"
-
-typedef void (*ParseXML) (const gchar *xml_result, gpointer user_data);
-
-typedef struct {
- GFlickr *flickr;
- ParseXML parse_xml;
- GFlickrHashTableCb hashtable_cb;
- GFlickrListCb list_cb;
- gpointer user_data;
-} GFlickrData;
-
-struct _GFlickrPrivate {
- gchar *api_key;
- gchar *auth_secret;
- gchar *auth_token;
- gint per_page;
-
- GrlNetWc *wc;
-};
-
-static void g_flickr_finalize (GObject *object);
-
-/* -------------------- GOBJECT -------------------- */
-
-G_DEFINE_TYPE (GFlickr, g_flickr, G_TYPE_OBJECT);
-
-static void
-g_flickr_class_init (GFlickrClass *klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- gobject_class->finalize = g_flickr_finalize;
-
- g_type_class_add_private (klass, sizeof (GFlickrPrivate));
-}
-
-static void
-g_flickr_init (GFlickr *f)
-{
- f->priv = G_FLICKR_GET_PRIVATE (f);
- f->priv->per_page = 100;
-}
-
-static void
-g_flickr_finalize (GObject *object)
-{
- GFlickr *f = G_FLICKR (object);
- g_free (f->priv->api_key);
- g_free (f->priv->auth_token);
- g_free (f->priv->auth_secret);
-
- if (f->priv->wc)
- g_object_unref (f->priv->wc);
-
- G_OBJECT_CLASS (g_flickr_parent_class)->finalize (object);
-}
-
-GFlickr *
-g_flickr_new (const gchar *api_key, const gchar *auth_secret, const gchar *auth_token)
-{
- g_return_val_if_fail (api_key && auth_secret, NULL);
-
- GFlickr *f = g_object_new (G_FLICKR_TYPE, NULL);
- f->priv->api_key = g_strdup (api_key);
- f->priv->auth_secret = g_strdup (auth_secret);
- f->priv->auth_token = g_strdup (auth_token);
-
- return f;
-}
-
-/* -------------------- PRIVATE API -------------------- */
-
-static gchar *
-get_api_sig (const gchar *secret, ...)
-{
- GHashTable *hash;
- GList *key_iter;
- GList *keys;
- GString *to_sign;
- gchar *api_sig;
- gchar *key;
- gchar *value;
- gint text_size = strlen (secret);
- va_list va_params;
-
- hash = g_hash_table_new (g_str_hash, g_str_equal);
-
- va_start (va_params, secret);
- while ((key = va_arg (va_params, gchar *))) {
- text_size += strlen (key);
- value = va_arg (va_params, gchar *);
- text_size += strlen (value);
- g_hash_table_insert (hash, key, value);
- }
- va_end (va_params);
-
- to_sign = g_string_sized_new (text_size);
- g_string_append (to_sign, secret);
-
- keys = g_hash_table_get_keys (hash);
- keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
- for (key_iter = keys; key_iter; key_iter = g_list_next (key_iter)) {
- g_string_append (to_sign, key_iter->data);
- g_string_append (to_sign, g_hash_table_lookup (hash, key_iter->data));
- }
-
- api_sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, to_sign->str, -1);
- g_hash_table_unref (hash);
- g_list_free (keys);
- g_string_free (to_sign, TRUE);
-
- return api_sig;
-}
-
-static gchar *
-get_xpath_element (const gchar *content,
- const gchar *xpath_element)
-{
- gchar *element = NULL;
- xmlDocPtr xmldoc = NULL;
- xmlXPathContextPtr xpath_ctx = NULL;
- xmlXPathObjectPtr xpath_res = NULL;
-
- xmldoc = xmlReadMemory (content, xmlStrlen ((xmlChar *) content), NULL, NULL,
- XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- if (xmldoc) {
- xpath_ctx = xmlXPathNewContext (xmldoc);
- if (xpath_ctx) {
- xpath_res = xmlXPathEvalExpression ((xmlChar *) xpath_element, xpath_ctx);
- if (xpath_res && xpath_res->nodesetval->nodeTab) {
- element =
- (gchar *) xmlNodeListGetString (xmldoc,
- xpath_res->nodesetval->nodeTab[0]->xmlChildrenNode,
- 1);
- }
- }
- }
-
- /* Free data */
- if (xmldoc) {
- xmlFreeDoc (xmldoc);
- }
-
- if (xpath_ctx) {
- xmlXPathFreeContext (xpath_ctx);
- }
-
- if (xpath_res) {
- xmlXPathFreeObject (xpath_res);
- }
-
- return element;
-}
-
-static gboolean
-result_is_correct (xmlNodePtr node)
-{
- gboolean correct = FALSE;
- xmlChar *stat;
-
- if (xmlStrcmp (node->name, (const xmlChar *) "rsp") == 0) {
- stat = xmlGetProp (node, (const xmlChar *) "stat");
- if (stat && xmlStrcmp (stat, (const xmlChar *) "ok") == 0) {
- correct = TRUE;
- xmlFree (stat);
- }
- }
-
- return correct;
-}
-
-static void
-add_node (xmlNodePtr node, GHashTable *photo)
-{
- xmlAttrPtr attr;
-
- for (attr = node->properties; attr != NULL; attr = attr->next) {
- g_hash_table_insert (photo,
- g_strconcat ((const gchar *) node->name,
- "_",
- (const gchar *) attr->name,
- NULL),
- (gchar *) xmlGetProp (node, attr->name));
- }
-}
-
-static GHashTable *
-get_photo (xmlNodePtr node)
-{
- GHashTable *photo = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- g_free,
- g_free);
-
- /* Add photo node */
- add_node (node, photo);
-
- /* Add children nodes with their properties */
-
- node = node->xmlChildrenNode;
-
- while (node) {
- if (xmlStrcmp (node->name, (const xmlChar *) "owner") == 0 ||
- xmlStrcmp (node->name, (const xmlChar *) "dates") == 0) {
- add_node (node, photo);
- } else if (xmlStrcmp (node->name, (const xmlChar *) "title") == 0 ||
- xmlStrcmp (node->name, (const xmlChar *) "description") == 0) {
- g_hash_table_insert (photo,
- g_strdup ((const gchar *) node->name),
- (gchar *) xmlNodeGetContent (node));
- }
-
- node = node->next;
- }
-
- return photo;
-}
-
-static GHashTable *
-get_photoset (xmlNodePtr node)
-{
- GHashTable *photoset = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- g_free,
- g_free);
-
- /* Add photoset node */
- add_node (node, photoset);
-
- /* Add children nodes with their properties */
- node = node->xmlChildrenNode;
-
- while (node) {
- g_hash_table_insert (photoset,
- g_strdup ((const gchar *) node->name),
- (gchar *) xmlNodeGetContent (node));
- node = node->next;
- }
-
- return photoset;
-}
-
-static gchar *
-get_tag (xmlNodePtr node)
-{
- if (xmlStrcmp (node->name, (const xmlChar *) "tag") == 0) {
- return (gchar *) xmlNodeGetContent (node);
- } else {
- return NULL;
- }
-}
-
-static GHashTable *
-get_token_info (xmlNodePtr node)
-{
- GHashTable *token = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- g_free,
- g_free);
- node = node->xmlChildrenNode;
-
- while (node) {
- g_hash_table_insert (token,
- g_strdup ((const gchar *) node->name),
- (gchar *) xmlNodeGetContent (node));
- add_node (node, token);
- node = node->next;
- }
-
- return token;
-}
-
-static void
-process_photo_result (const gchar *xml_result, gpointer user_data)
-{
- xmlDocPtr doc;
- xmlNodePtr node;
- GFlickrData *data = (GFlickrData *) user_data;
- GHashTable *photo;
-
- doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
- NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- node = xmlDocGetRootElement (doc);
-
- /* Check result is ok */
- if (!node || !result_is_correct (node)) {
- data->hashtable_cb (data->flickr, NULL, data->user_data);
- } else {
- node = node->xmlChildrenNode;
-
- photo = get_photo (node);
- data->hashtable_cb (data->flickr, photo, data->user_data);
- g_hash_table_unref (photo);
- }
- g_object_unref (data->flickr);
- g_slice_free (GFlickrData, data);
- xmlFreeDoc (doc);
-}
-
-static void
-process_photolist_result (const gchar *xml_result, gpointer user_data)
-{
- GFlickrData *data = (GFlickrData *) user_data;
- GList *photolist = NULL;
- xmlDocPtr doc;
- xmlNodePtr node;
-
- doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
- NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- node = xmlDocGetRootElement (doc);
-
- /* Check result is ok */
- if (!node || !result_is_correct (node)) {
- data->list_cb (data->flickr, NULL, data->user_data);
- } else {
- node = node->xmlChildrenNode;
-
- /* Now we're at "photo pages" node */
- node = node->xmlChildrenNode;
- while (node) {
- photolist = g_list_prepend (photolist, get_photo (node));
- node = node->next;
- }
-
- data->list_cb (data->flickr, g_list_reverse (photolist), data->user_data);
- g_list_foreach (photolist, (GFunc) g_hash_table_unref, NULL);
- g_list_free (photolist);
- }
- g_object_unref (data->flickr);
- g_slice_free (GFlickrData, data);
- xmlFreeDoc (doc);
-}
-
-static void
-process_taglist_result (const gchar *xml_result, gpointer user_data)
-{
- GFlickrData *data = (GFlickrData *) user_data;
- GList *taglist = NULL;
- xmlDocPtr doc;
- xmlNodePtr node;
-
- doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
- NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- node = xmlDocGetRootElement (doc);
-
- /* Check if result is OK */
- if (!node || !result_is_correct (node)) {
- data->list_cb (data->flickr, NULL, data->user_data);
- } else {
- node = node->xmlChildrenNode;
-
- /* Now we're at "hot tags" node */
- node = node->xmlChildrenNode;
- while (node) {
- taglist = g_list_prepend (taglist, get_tag (node));
- node = node->next;
- }
-
- data->list_cb (data->flickr, g_list_reverse (taglist), data->user_data);
- g_list_foreach (taglist, (GFunc) g_free, NULL);
- g_list_free (taglist);
- }
- g_object_unref (data->flickr);
- g_slice_free (GFlickrData, data);
- xmlFreeDoc (doc);
-}
-
-static void
-process_photosetslist_result (const gchar *xml_result, gpointer user_data)
-{
- GFlickrData *data = (GFlickrData *) user_data;
- GList *photosets = NULL;
- xmlDocPtr doc;
- xmlNodePtr node;
-
- doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
- NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- node = xmlDocGetRootElement (doc);
-
- /* Check if result is OK */
- if (!node || !result_is_correct (node)) {
- data->list_cb (data->flickr, NULL, data->user_data);
- } else {
- node = node->xmlChildrenNode;
-
- /* Now we're at "photosets" node */
- node = node->xmlChildrenNode;
- while (node) {
- photosets = g_list_prepend (photosets, get_photoset (node));
- node = node->next;
- }
-
- data->list_cb (data->flickr, g_list_reverse (photosets), data->user_data);
- g_list_foreach (photosets, (GFunc) g_hash_table_unref, NULL);
- g_list_free (photosets);
- }
- g_object_unref (data->flickr);
- g_slice_free (GFlickrData, data);
- xmlFreeDoc (doc);
-}
-
-static void
-process_photosetsphotos_result (const gchar *xml_result, gpointer user_data)
-{
- GFlickrData *data = (GFlickrData *) user_data;
- GList *list = NULL;
- xmlDocPtr doc;
- xmlNodePtr node;
-
- doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
- NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- node = xmlDocGetRootElement (doc);
-
- /* Check result is ok */
- if (!node || !result_is_correct (node)) {
- data->list_cb (data->flickr, NULL, data->user_data);
- } else {
- node = node->xmlChildrenNode;
-
- /* Now we're at "photoset page" node */
- node = node->xmlChildrenNode;
- while (node) {
- list = g_list_prepend (list, get_photo (node));
- node = node->next;
- }
-
- data->list_cb (data->flickr, g_list_reverse (list), data->user_data);
- g_list_foreach (list, (GFunc) g_hash_table_unref, NULL);
- g_list_free (list);
- }
- g_object_unref (data->flickr);
- g_slice_free (GFlickrData, data);
- xmlFreeDoc (doc);
-}
-
-static void
-process_token_result (const gchar *xml_result, gpointer user_data)
-{
- xmlDocPtr doc;
- xmlNodePtr node;
- GFlickrData *data = (GFlickrData *) user_data;
- GHashTable *token;
-
- doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
- NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- node = xmlDocGetRootElement (doc);
-
- /* Check if result is OK */
- if (!node || !result_is_correct (node)) {
- data->hashtable_cb (data->flickr, NULL, data->user_data);
- } else {
- node = node->xmlChildrenNode;
- token = get_token_info (node);
- data->hashtable_cb (data->flickr, token, data->user_data);
- g_hash_table_unref (token);
- }
-
- g_object_unref (data->flickr);
- g_slice_free (GFlickrData, data);
- xmlFreeDoc (doc);
-}
-
-inline static GrlNetWc *
-get_wc (GFlickr *f)
-{
- if (!f->priv->wc)
- f->priv->wc = grl_net_wc_new ();
-
- return f->priv->wc;
-}
-
-static void
-read_done_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- gchar *content = NULL;
- GError *wc_error = NULL;
- GFlickrData *data = (GFlickrData *) user_data;
-
- grl_net_wc_request_finish (GRL_NET_WC (source_object),
- res,
- &content,
- NULL,
- &wc_error);
-
- data->parse_xml (content, user_data);
-}
-
-static void
-read_url_async (GFlickr *f,
- const gchar *url,
- gpointer user_data)
-{
- GRL_DEBUG ("Opening '%s'", url);
- grl_net_wc_request_async (get_wc (f),
- url,
- NULL,
- read_done_cb,
- user_data);
-}
-
-/* -------------------- PUBLIC API -------------------- */
-
-void
-g_flickr_set_per_page (GFlickr *f, gint per_page)
-{
- g_return_if_fail (G_IS_FLICKR (f));
-
- f->priv->per_page = per_page;
-}
-
-void
-g_flickr_photos_getInfo (GFlickr *f,
- glong photo_id,
- GFlickrHashTableCb callback,
- gpointer user_data)
-{
- gchar *auth;
-
- g_return_if_fail (G_IS_FLICKR (f));
-
- gchar *str_photo_id = g_strdup_printf ("%ld", photo_id);
- gchar *api_sig = get_api_sig (f->priv->auth_secret,
- "api_key", f->priv->api_key,
- "method", FLICKR_PHOTOS_GETINFO_METHOD,
- "photo_id", str_photo_id,
- f->priv->auth_token? "auth_token": "",
- f->priv->auth_token? f->priv->auth_token: "",
- NULL);
- g_free (str_photo_id);
-
- /* Build the request */
- if (f->priv->auth_token) {
- auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
- } else {
- auth = g_strdup ("");
- }
-
- gchar *request = g_strdup_printf (FLICKR_PHOTOS_GETINFO,
- f->priv->api_key,
- api_sig,
- photo_id,
- auth);
- g_free (api_sig);
- g_free (auth);
-
- GFlickrData *gfd = g_slice_new (GFlickrData);
- gfd->flickr = g_object_ref (f);
- gfd->parse_xml = process_photo_result;
- gfd->hashtable_cb = callback;
- gfd->user_data = user_data;
-
- read_url_async (f, request, gfd);
- g_free (request);
-}
-
-void
-g_flickr_photos_search (GFlickr *f,
- const gchar *user_id,
- const gchar *text,
- const gchar *tags,
- gint page,
- GFlickrListCb callback,
- gpointer user_data)
-{
- gchar *auth;
- g_return_if_fail (G_IS_FLICKR (f));
-
- if (!user_id) {
- user_id = "";
- }
-
- if (!text) {
- text = "";
- }
-
- if (!tags) {
- tags = "";
- }
-
- gchar *strpage = g_strdup_printf ("%d", page);
- gchar *strperpage = g_strdup_printf ("%d", f->priv->per_page);
-
- gchar *api_sig =
- get_api_sig (f->priv->auth_secret,
- "api_key", f->priv->api_key,
- "extras", "media,date_taken,owner_name,url_o,url_t",
- "method", FLICKR_PHOTOS_SEARCH_METHOD,
- "user_id", user_id,
- "page", strpage,
- "per_page", strperpage,
- "tags", tags,
- "text", text,
- f->priv->auth_token? "auth_token": "",
- f->priv->auth_token? f->priv->auth_token: "",
- NULL);
- g_free (strpage);
- g_free (strperpage);
-
- /* Build the request */
- if (f->priv->auth_token) {
- auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
- } else {
- auth = g_strdup ("");
- }
-
- gchar *request = g_strdup_printf (FLICKR_PHOTOS_SEARCH,
- f->priv->api_key,
- api_sig,
- user_id,
- f->priv->per_page,
- page,
- tags,
- text,
- auth);
- g_free (api_sig);
- g_free (auth);
-
- GFlickrData *gfd = g_slice_new (GFlickrData);
- gfd->flickr = g_object_ref (f);
- gfd->parse_xml = process_photolist_result;
- gfd->list_cb = callback;
- gfd->user_data = user_data;
-
- read_url_async (f, request, gfd);
- g_free (request);
-}
-
-void
-g_flickr_photos_getRecent (GFlickr *f,
- gint page,
- GFlickrListCb callback,
- gpointer user_data)
-{
- gchar *auth;
- g_return_if_fail (G_IS_FLICKR (f));
-
- gchar *strpage = g_strdup_printf ("%d", page);
- gchar *strperpage = g_strdup_printf ("%d", f->priv->per_page);
-
- gchar *api_sig =
- get_api_sig (f->priv->auth_secret,
- "api_key", f->priv->api_key,
- "extras", "media,date_taken,owner_name,url_o,url_t",
- "method", FLICKR_PHOTOS_GETRECENT_METHOD,
- "page", strpage,
- "per_page", strperpage,
- f->priv->auth_token? "auth_token": "",
- f->priv->auth_token? f->priv->auth_token: "",
- NULL);
- g_free (strpage);
- g_free (strperpage);
-
- /* Build the request */
- if (f->priv->auth_token) {
- auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
- } else {
- auth = g_strdup ("");
- }
-
- gchar *request = g_strdup_printf (FLICKR_PHOTOS_GETRECENT,
- f->priv->api_key,
- api_sig,
- f->priv->per_page,
- page,
- auth);
- g_free (api_sig);
- g_free (auth);
-
- GFlickrData *gfd = g_slice_new (GFlickrData);
- gfd->flickr = g_object_ref (f);
- gfd->parse_xml = process_photolist_result;
- gfd->list_cb = callback;
- gfd->user_data = user_data;
-
- read_url_async (f, request, gfd);
- g_free (request);
-}
-
-gchar *
-g_flickr_photo_url_original (GFlickr *f, GHashTable *photo)
-{
- gchar *extension;
- gchar *farm_id;
- gchar *o_secret;
- gchar *photo_id;
- gchar *server_id;
-
- if (!photo) {
- return NULL;
- }
-
- extension = g_hash_table_lookup (photo, "photo_originalformat");
- farm_id = g_hash_table_lookup (photo, "photo_farm");
- o_secret = g_hash_table_lookup (photo, "photo_originalsecret");
- photo_id = g_hash_table_lookup (photo, "photo_id");
- server_id = g_hash_table_lookup (photo, "photo_server");
-
- if (!extension || !farm_id || !o_secret || !photo_id || !server_id) {
- return NULL;
- } else {
- return g_strdup_printf (FLICKR_PHOTO_ORIG_URL,
- farm_id,
- server_id,
- photo_id,
- o_secret,
- extension);
- }
-}
-
-gchar *
-g_flickr_photo_url_thumbnail (GFlickr *f, GHashTable *photo)
-{
- gchar *farm_id;
- gchar *secret;
- gchar *photo_id;
- gchar *server_id;
-
- if (!photo) {
- return NULL;
- }
-
- farm_id = g_hash_table_lookup (photo, "photo_farm");
- secret = g_hash_table_lookup (photo, "photo_secret");
- photo_id = g_hash_table_lookup (photo, "photo_id");
- server_id = g_hash_table_lookup (photo, "photo_server");
-
- if (!farm_id || !secret || !photo_id || !server_id) {
- return NULL;
- } else {
- return g_strdup_printf (FLICKR_PHOTO_THUMB_URL,
- farm_id,
- server_id,
- photo_id,
- secret);
- }
-}
-
-gchar *
-g_flickr_photo_url_largest (GFlickr *f, GHashTable *photo)
-{
- gchar *farm_id;
- gchar *secret;
- gchar *photo_id;
- gchar *server_id;
-
- if (!photo) {
- return NULL;
- }
-
- farm_id = g_hash_table_lookup (photo, "photo_farm");
- secret = g_hash_table_lookup (photo, "photo_secret");
- photo_id = g_hash_table_lookup (photo, "photo_id");
- server_id = g_hash_table_lookup (photo, "photo_server");
-
- if (!farm_id || !secret || !photo_id || !server_id) {
- return NULL;
- } else {
- return g_strdup_printf (FLICKR_PHOTO_LARGEST_URL,
- farm_id,
- server_id,
- photo_id,
- secret);
- }
-}
-
-void
-g_flickr_tags_getHotList (GFlickr *f,
- gint count,
- GFlickrListCb callback,
- gpointer user_data)
-{
- gchar *auth;
-
- g_return_if_fail (G_IS_FLICKR (f));
-
- gchar *strcount = g_strdup_printf ("%d", count);
-
- gchar *api_sig = get_api_sig (f->priv->auth_secret,
- "api_key", f->priv->api_key,
- "count", strcount,
- "method", FLICKR_TAGS_GETHOTLIST_METHOD,
- f->priv->auth_token? "auth_token": "",
- f->priv->auth_token? f->priv->auth_token: "",
- NULL);
- g_free (strcount);
-
- /* Build the request */
- if (f->priv->auth_token) {
- auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
- } else {
- auth = g_strdup ("");
- }
- gchar *request = g_strdup_printf (FLICKR_TAGS_GETHOTLIST,
- f->priv->api_key,
- api_sig,
- count,
- auth);
- g_free (api_sig);
- g_free (auth);
-
- GFlickrData *gfd = g_slice_new (GFlickrData);
- gfd->flickr = g_object_ref (f);
- gfd->parse_xml = process_taglist_result;
- gfd->list_cb = callback;
- gfd->user_data = user_data;
-
- read_url_async (f, request, gfd);
- g_free (request);
-}
-
-void
-g_flickr_photosets_getList (GFlickr *f,
- const gchar *user_id,
- GFlickrListCb callback,
- gpointer user_data)
-{
- gchar *user;
- gchar *auth;
-
- gchar *api_sig = get_api_sig (f->priv->auth_secret,
- "api_key", f->priv->api_key,
- "method", FLICKR_PHOTOSETS_GETLIST_METHOD,
- user_id? "user_id": "",
- user_id? user_id: "",
- f->priv->auth_token? "auth_token": "",
- f->priv->auth_token? f->priv->auth_token: "",
- NULL);
-
- /* Build the request */
- if (user_id) {
- user = g_strdup_printf ("&user_id=%s", user_id);
- } else {
- user = g_strdup ("");
- }
-
- if (f->priv->auth_token) {
- auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
- } else {
- auth = g_strdup ("");
- }
-
- gchar *request = g_strdup_printf (FLICKR_PHOTOSETS_GETLIST,
- f->priv->api_key,
- api_sig,
- user,
- auth);
-
- g_free (api_sig);
- g_free (user);
- g_free (auth);
-
- GFlickrData *gfd = g_slice_new (GFlickrData);
- gfd->flickr = g_object_ref (f);
- gfd->parse_xml = process_photosetslist_result;
- gfd->list_cb = callback;
- gfd->user_data = user_data;
-
- read_url_async (f, request, gfd);
- g_free (request);
-}
-
-void
-g_flickr_photosets_getPhotos (GFlickr *f,
- const gchar *photoset_id,
- gint page,
- GFlickrListCb callback,
- gpointer user_data)
-{
- gchar *auth;
-
- g_return_if_fail (G_IS_FLICKR (f));
- g_return_if_fail (photoset_id);
-
- gchar *strpage = g_strdup_printf ("%d", page);
- gchar *strperpage = g_strdup_printf ("%d", f->priv->per_page);
-
- gchar *api_sig =
- get_api_sig (f->priv->auth_secret,
- "api_key", f->priv->api_key,
- "photoset_id", photoset_id,
- "extras", "media,date_taken,owner_name,url_o,url_t",
- "method", FLICKR_PHOTOSETS_GETPHOTOS_METHOD,
- "page", strpage,
- "per_page", strperpage,
- f->priv->auth_token? "auth_token": "",
- f->priv->auth_token? f->priv->auth_token: "",
- NULL);
-
- g_free (strpage);
- g_free (strperpage);
-
- /* Build the request */
- if (f->priv->auth_token) {
- auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
- } else {
- auth = g_strdup ("");
- }
-
- gchar *request = g_strdup_printf (FLICKR_PHOTOSETS_GETPHOTOS,
- f->priv->api_key,
- api_sig,
- photoset_id,
- f->priv->per_page,
- page,
- auth);
- g_free (api_sig);
- g_free (auth);
-
- GFlickrData *gfd = g_slice_new (GFlickrData);
- gfd->flickr = g_object_ref (f);
- gfd->parse_xml = process_photosetsphotos_result;
- gfd->list_cb = callback;
- gfd->user_data = user_data;
-
- read_url_async (f, request, gfd);
- g_free (request);
-}
-
-gchar *
-g_flickr_auth_getFrob (GFlickr *f)
-{
- gchar *api_sig;
- gchar *url;
- GVfs *vfs;
- GFile *uri;
- gchar *contents;
- GError *error = NULL;
- gchar *frob = NULL;
-
- g_return_val_if_fail (G_IS_FLICKR (f), NULL);
-
- api_sig = get_api_sig (f->priv->auth_secret,
- "api_key", f->priv->api_key,
- "method", "flickr.auth.getFrob",
- NULL);
-
- /* Build url */
- url = g_strdup_printf (FLICKR_AUTH_GETFROB,
- f->priv->api_key,
- api_sig);
- g_free (api_sig);
-
- /* Load content */
- vfs = g_vfs_get_default ();
- uri = g_vfs_get_file_for_uri (vfs, url);
- g_free (url);
- if (!g_file_load_contents (uri, NULL, &contents, NULL, NULL, &error)) {
- GRL_WARNING ("Unable to get Flickr's frob: %s", error->message);
- return NULL;
- }
-
- /* Get frob */
- frob = get_xpath_element (contents, "/rsp/frob");
- g_free (contents);
- if (!frob) {
- GRL_WARNING ("Can not get Flickr's frob");
- }
-
- return frob;
-}
-
-gchar *
-g_flickr_auth_loginLink (GFlickr *f,
- const gchar *frob,
- const gchar *perm)
-{
- gchar *api_sig;
- gchar *url;
-
- g_return_val_if_fail (G_IS_FLICKR (f), NULL);
- g_return_val_if_fail (frob, NULL);
- g_return_val_if_fail (perm, NULL);
-
- api_sig = get_api_sig (f->priv->auth_secret,
- "api_key", f->priv->api_key,
- "frob", frob,
- "perms", perm,
- NULL);
-
- url = g_strdup_printf (FLICKR_AUTH_LOGINLINK,
- f->priv->api_key,
- api_sig,
- frob,
- perm);
- g_free (api_sig);
-
- return url;
-}
-
-gchar *
-g_flickr_auth_getToken (GFlickr *f,
- const gchar *frob)
-{
- GError *error = NULL;
- GFile *uri;
- GVfs *vfs;
- gchar *api_sig;
- gchar *contents;
- gchar *token;
- gchar *url;
-
- g_return_val_if_fail (G_IS_FLICKR (f), NULL);
- g_return_val_if_fail (frob, NULL);
-
- api_sig = get_api_sig (f->priv->auth_secret,
- "method", "flickr.auth.getToken",
- "api_key", f->priv->api_key,
- "frob", frob,
- NULL);
-
- /* Build url */
- url = g_strdup_printf (FLICKR_AUTH_GETTOKEN,
- f->priv->api_key,
- api_sig,
- frob);
- g_free (api_sig);
-
- /* Load content */
- vfs = g_vfs_get_default ();
- uri = g_vfs_get_file_for_uri (vfs, url);
- g_free (url);
- if (!g_file_load_contents (uri, NULL, &contents, NULL, NULL, &error)) {
- GRL_WARNING ("Unable to get Flickr's token: %s", error->message);
- return NULL;
- }
-
- /* Get token */
- token = get_xpath_element (contents, "/rsp/auth/token");
- g_free (contents);
- if (!token) {
- GRL_WARNING ("Can not get Flickr's token");
- }
-
- return token;
-}
-
-void
-g_flickr_auth_checkToken (GFlickr *f,
- const gchar *token,
- GFlickrHashTableCb callback,
- gpointer user_data)
-{
- gchar *api_sig;
- gchar *request;
-
- g_return_if_fail (G_IS_FLICKR (f));
- g_return_if_fail (token);
- g_return_if_fail (callback);
-
- api_sig = get_api_sig (f->priv->auth_secret,
- "method", FLICKR_AUTH_CHECKTOKEN_METHOD,
- "api_key", f->priv->api_key,
- "auth_token", token,
- NULL);
-
- /* Build request */
- request = g_strdup_printf (FLICKR_AUTH_CHECKTOKEN,
- f->priv->api_key,
- api_sig,
- token);
- g_free (api_sig);
-
- GFlickrData *gfd = g_slice_new (GFlickrData);
- gfd->flickr = g_object_ref (f);
- gfd->parse_xml = process_token_result;
- gfd->hashtable_cb = callback;
- gfd->user_data = user_data;
-
- read_url_async (f, request, gfd);
- g_free (request);
-}
diff --git a/src/flickr/gflickr.h b/src/flickr/gflickr.h
deleted file mode 100644
index dd583d8..0000000
--- a/src/flickr/gflickr.h
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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 _G_FLICKR_H_
-#define _G_FLICKR_H_
-
-#include <glib-object.h>
-
-#define G_FLICKR_TYPE \
- (g_flickr_get_type ())
-
-#define G_FLICKR(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- G_FLICKR_TYPE, \
- GFlickr))
-
-#define G_IS_FLICKR(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- G_FLICKR_TYPE))
-
-#define G_FLICKR_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- G_FLICKR_TYPE, \
- GFlickrClass))
-
-#define G_IS_FLICKR_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- G_FLICKR_TYPE))
-
-#define G_FLICKR_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- G_FLICKR_TYPE, \
- GFlickrClass))
-
-typedef struct _GFlickr GFlickr;
-typedef struct _GFlickrPrivate GFlickrPrivate;
-
-struct _GFlickr {
-
- GObject parent;
-
- /*< private >*/
- GFlickrPrivate *priv;
-};
-
-typedef struct _GFlickrClass GFlickrClass;
-
-struct _GFlickrClass {
-
- GObjectClass parent_class;
-
-};
-
-
-typedef void (*GFlickrHashTableCb) (GFlickr *f, GHashTable *result, gpointer user_data);
-
-typedef void (*GFlickrListCb) (GFlickr *f, GList *result, gpointer user_data);
-
-typedef void (*GFlickrCheckToken) (GFlickr *f, GHashTable *tokeninfo, gpointer user_data);
-
-GType g_flickr_get_type (void);
-
-GFlickr *g_flickr_new (const gchar *api_key, const gchar *auth_secret, const gchar *auth_token);
-
-void g_flickr_set_per_page (GFlickr *f, gint per_page);
-
-void
-g_flickr_photos_getInfo (GFlickr *f,
- glong photo_id,
- GFlickrHashTableCb callback,
- gpointer user_data);
-
-void
-g_flickr_photos_search (GFlickr *f,
- const gchar *user_id,
- const gchar *text,
- const gchar *tags,
- gint page,
- GFlickrListCb callback,
- gpointer user_data);
-
-void
-g_flickr_photos_getRecent (GFlickr *f,
- gint page,
- GFlickrListCb callback,
- gpointer user_data);
-
-gchar *
-g_flickr_photo_url_original (GFlickr *f, GHashTable *photo);
-
-gchar *
-g_flickr_photo_url_thumbnail (GFlickr *f, GHashTable *photo);
-
-gchar *
-g_flickr_photo_url_largest (GFlickr *f, GHashTable *photo);
-
-void
-g_flickr_tags_getHotList (GFlickr *f,
- gint count,
- GFlickrListCb callback,
- gpointer user_data);
-
-void
-g_flickr_photosets_getList (GFlickr *f,
- const gchar *user_id,
- GFlickrListCb callback,
- gpointer user_data);
-
-void
-g_flickr_photosets_getPhotos (GFlickr *f,
- const gchar *photoset_id,
- gint page,
- GFlickrListCb callback,
- gpointer user_data);
-
-gchar *
-g_flickr_auth_getFrob (GFlickr *f);
-
-gchar *
-g_flickr_auth_loginLink (GFlickr *f,
- const gchar *frob,
- const gchar *perm);
-
-gchar *
-g_flickr_auth_getToken (GFlickr *f,
- const gchar *frob);
-
-void
-g_flickr_auth_checkToken (GFlickr *f,
- const gchar *token,
- GFlickrHashTableCb callback,
- gpointer user_data);
-
-#endif /* _G_FLICKR_H_ */
diff --git a/src/flickr/grl-flickr.c b/src/flickr/grl-flickr.c
deleted file mode 100644
index 6065355..0000000
--- a/src/flickr/grl-flickr.c
+++ /dev/null
@@ -1,755 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- * Copyright (C) 2011 Intel Corporation.
- *
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
-#include <string.h>
-#include <stdlib.h>
-
-#include "grl-flickr.h"
-#include "gflickr.h"
-
-#define GRL_FLICKR_SOURCE_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_FLICKR_SOURCE_TYPE, \
- GrlFlickrSourcePrivate))
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT flickr_log_domain
-GRL_LOG_DOMAIN(flickr_log_domain);
-
-#define SEARCH_MAX 500
-#define HOTLIST_MAX 200
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID FLICKR_PLUGIN_ID
-
-#define PUBLIC_SOURCE_ID "grl-flickr"
-#define PUBLIC_SOURCE_NAME "Flickr"
-#define PUBLIC_SOURCE_DESC "A source for browsing and searching Flickr photos"
-
-#define PERSONAL_SOURCE_ID "grl-flickr-%s"
-#define PERSONAL_SOURCE_NAME "%s's Flickr"
-#define PERSONAL_SOURCE_DESC "A source for browsing and searching %s' flickr photos"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-typedef struct {
- GrlMediaSource *source;
- GrlMediaSourceResultCb callback;
- gchar *user_id;
- gchar *tags;
- gchar *text;
- guint offset;
- guint page;
- gpointer user_data;
- guint count;
- guint operation_id;
-} OperationData;
-
-struct _GrlFlickrSourcePrivate {
- GFlickr *flickr;
- gchar *user_id;
-};
-
-static void token_info_cb (GFlickr *f,
- GHashTable *info,
- gpointer user_data);
-
-static GrlFlickrSource *grl_flickr_source_public_new (const gchar *flickr_api_key,
- const gchar *flickr_secret);
-
-static void grl_flickr_source_personal_new (const GrlPluginInfo *plugin,
- const gchar *flickr_api_key,
- const gchar *flickr_secret,
- const gchar *flickr_token);
-
-static void grl_flickr_source_finalize (GObject *object);
-
-gboolean grl_flickr_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-
-static const GList *grl_flickr_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_flickr_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-
-static void grl_flickr_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ss);
-
-static void grl_flickr_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-
-/* =================== Flickr Plugin =============== */
-
-gboolean
-grl_flickr_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- gchar *flickr_key;
- gchar *flickr_secret;
- gchar *flickr_token;
- GrlConfig *config;
- gboolean public_source_created = FALSE;
-
- GRL_LOG_DOMAIN_INIT (flickr_log_domain, "flickr");
-
- GRL_DEBUG ("flickr_plugin_init");
-
- if (!configs) {
- GRL_WARNING ("Missing configuration");
- return FALSE;
- }
-
- while (configs) {
- config = GRL_CONFIG (configs->data);
-
- flickr_key = grl_config_get_api_key (config);
- flickr_token = grl_config_get_api_token (config);
- flickr_secret = grl_config_get_api_secret (config);
-
- if (!flickr_key || !flickr_secret) {
- GRL_WARNING ("Required configuration keys not set up");
- } else if (flickr_token) {
- grl_flickr_source_personal_new (plugin,
- flickr_key,
- flickr_secret,
- flickr_token);
- } else if (public_source_created) {
- GRL_WARNING ("Only one public source can be created");
- } else {
- GrlFlickrSource *source = grl_flickr_source_public_new (flickr_key, flickr_secret);
- public_source_created = TRUE;
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- }
-
- if (flickr_key != NULL)
- g_free (flickr_key);
- if (flickr_token != NULL)
- g_free (flickr_token);
- if (flickr_secret != NULL)
- g_free (flickr_secret);
-
- configs = g_list_next (configs);
- }
-
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_flickr_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Flickr GObject ================ */
-
-G_DEFINE_TYPE (GrlFlickrSource, grl_flickr_source, GRL_TYPE_MEDIA_SOURCE);
-
-static GrlFlickrSource *
-grl_flickr_source_public_new (const gchar *flickr_api_key,
- const gchar *flickr_secret)
-{
- GrlFlickrSource *source;
-
- GRL_DEBUG ("grl_flickr_source_new");
-
- source = g_object_new (GRL_FLICKR_SOURCE_TYPE,
- "source-id", PUBLIC_SOURCE_ID,
- "source-name", PUBLIC_SOURCE_NAME,
- "source-desc", PUBLIC_SOURCE_DESC,
- NULL);
- source->priv->flickr = g_flickr_new (flickr_api_key, flickr_secret, NULL);
-
- return source;
-}
-
-static void
-grl_flickr_source_personal_new (const GrlPluginInfo *plugin,
- const gchar *flickr_api_key,
- const gchar *flickr_secret,
- const gchar *flickr_token)
-{
- GFlickr *f;
-
- f = g_flickr_new (flickr_api_key, flickr_secret, flickr_token);
- g_flickr_auth_checkToken (f, flickr_token, token_info_cb, (gpointer) plugin);
-}
-
-static void
-grl_flickr_source_class_init (GrlFlickrSourceClass * klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
-
- gobject_class->finalize = grl_flickr_source_finalize;
- source_class->browse = grl_flickr_source_browse;
- source_class->metadata = grl_flickr_source_metadata;
- source_class->search = grl_flickr_source_search;
- metadata_class->supported_keys = grl_flickr_source_supported_keys;
-
- g_type_class_add_private (klass, sizeof (GrlFlickrSourcePrivate));
-}
-
-static void
-grl_flickr_source_init (GrlFlickrSource *source)
-{
- source->priv = GRL_FLICKR_SOURCE_GET_PRIVATE (source);
-
- grl_media_source_set_auto_split_threshold (GRL_MEDIA_SOURCE (source),
- SEARCH_MAX);
-}
-
-static void
-grl_flickr_source_finalize (GObject *object)
-{
- GrlFlickrSource *source;
-
- GRL_DEBUG ("grl_flickr_source_finalize");
-
- source = GRL_FLICKR_SOURCE (object);
- g_free (source->priv->user_id);
-
- G_OBJECT_CLASS (grl_flickr_source_parent_class)->finalize (object);
-}
-
-/* ======================= Utilities ==================== */
-
-static void
-token_info_cb (GFlickr *f,
- GHashTable *info,
- gpointer user_data)
-{
- GrlFlickrSource *source;
- GrlPluginInfo *plugin = (GrlPluginInfo *) user_data;
- GrlPluginRegistry *registry;
- gchar *fullname;
- gchar *source_desc;
- gchar *source_id;
- gchar *source_name;
- gchar *username;
-
- if (!info) {
- GRL_WARNING ("Wrong token!");
- g_object_unref (f);
- return;
- }
-
- registry = grl_plugin_registry_get_default ();
-
- username = g_hash_table_lookup (info, "user_username");
- fullname = g_hash_table_lookup (info, "user_fullname");
-
- source_id = g_strdup_printf (PERSONAL_SOURCE_ID, username);
- source_name = g_strdup_printf (PERSONAL_SOURCE_NAME, fullname);
- source_desc = g_strdup_printf (PERSONAL_SOURCE_DESC, fullname);
-
- /* Check if source is already registered */
- if (grl_plugin_registry_lookup_source (registry, source_id)) {
- GRL_DEBUG ("A source with id '%s' is already registered. Skipping...",
- source_id);
- g_object_unref (f);
- } else {
- source = g_object_new (GRL_FLICKR_SOURCE_TYPE,
- "source-id", source_id,
- "source-name", source_name,
- "source-desc", source_desc,
- NULL);
- source->priv->flickr = f;
- source->priv->user_id = g_strdup (g_hash_table_lookup (info, "user_nsid"));
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- }
-
- g_free (source_id);
- g_free (source_name);
- g_free (source_desc);
-}
-
-static void
-update_media (GrlMedia *media, GHashTable *photo)
-{
- gchar *author;
- gchar *date;
- gchar *description;
- gchar *id;
- gchar *thumbnail;
- gchar *title;
- gchar *url;
-
- author = g_hash_table_lookup (photo, "owner_realname");
- if (!author) {
- author = g_hash_table_lookup (photo, "photo_ownername");
- }
- date = g_hash_table_lookup (photo, "dates_taken");
- if (!date) {
- date = g_hash_table_lookup (photo, "photo_datetaken");
- }
- description = g_hash_table_lookup (photo, "description");
- id = g_hash_table_lookup (photo, "photo_id");
- thumbnail = g_strdup (g_hash_table_lookup (photo, "photo_url_t"));
- if (!thumbnail) {
- thumbnail = g_flickr_photo_url_thumbnail (NULL, photo);
- }
- title = g_hash_table_lookup (photo, "title");
- if (!title) {
- title = g_hash_table_lookup (photo, "photo_title");
- }
- url = g_strdup (g_hash_table_lookup (photo, "photo_url_o"));
- if (!url) {
- url = g_flickr_photo_url_original (NULL, photo);
- if (!url) {
- url = g_flickr_photo_url_largest (NULL, photo);
- }
- }
-
- if (author) {
- grl_media_set_author (media, author);
- }
-
- if (date) {
- grl_media_set_date (media, date);
- }
-
- if (description && description[0] != '\0') {
- grl_media_set_description (media, description);
- }
-
- if (id) {
- grl_media_set_id (media, id);
- }
-
- if (thumbnail) {
- grl_media_set_thumbnail (media, thumbnail);
- g_free (thumbnail);
- }
-
- if (title && title[0] != '\0') {
- grl_media_set_title (media, title);
- }
-
- if (url) {
- grl_media_set_url (media, url);
- g_free (url);
- }
-}
-
-static void
-getInfo_cb (GFlickr *f, GHashTable *photo, gpointer user_data)
-{
- GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
-
- if (photo) {
- update_media (ms->media, photo);
- }
-
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
-}
-
-static void
-search_cb (GFlickr *f, GList *photolist, gpointer user_data)
-{
- GrlMedia *media;
- OperationData *od = (OperationData *) user_data;
- gchar *media_type;
-
- /* Go to offset element */
- photolist = g_list_nth (photolist, od->offset);
-
- /* No more elements can be sent */
- if (!photolist) {
- od->callback (od->source,
- od->operation_id,
- NULL,
- 0,
- od->user_data,
- NULL);
- g_slice_free (OperationData, od);
- return;
- }
-
- while (photolist && od->count) {
- media_type = g_hash_table_lookup (photolist->data, "photo_media");
- if (strcmp (media_type, "photo") == 0) {
- media = grl_media_image_new ();
- } else {
- media = grl_media_video_new ();
- }
- update_media (media, photolist->data);
- od->callback (od->source,
- od->operation_id,
- media,
- od->count == 1? 0: -1,
- od->user_data,
- NULL);
- photolist = g_list_next (photolist);
- od->count--;
- }
-
- /* Get more elements */
- if (od->count) {
- od->offset = 0;
- od->page++;
- g_flickr_photos_search (f,
- od->user_id,
- od->text,
- od->tags,
- od->page,
- search_cb,
- od);
- } else {
- g_slice_free (OperationData, od);
- }
-}
-
-static void
-photosetslist_cb (GFlickr *f, GList *photosets, gpointer user_data)
-{
- GrlMedia *media;
- GrlMediaSourceBrowseSpec *bs = (GrlMediaSourceBrowseSpec *) user_data;
- gchar *value;
- gint count;
-
- /* Go to offset element */
- photosets = g_list_nth (photosets, bs->skip);
-
- /* No more elements can be sent */
- if (!photosets) {
- bs->callback (bs->source,
- bs->browse_id,
- NULL,
- 0,
- bs->user_data,
- NULL);
- return;
- }
-
- /* Send data */
- count = g_list_length (photosets);
- if (count > bs->count) {
- count = bs->count;
- }
-
- while (photosets && count > 0) {
- count--;
- media = grl_media_box_new ();
- grl_media_set_id (media,
- g_hash_table_lookup (photosets->data,
- "photoset_id"));
- value = g_hash_table_lookup (photosets->data, "title");
- if (value && value[0] != '\0') {
- grl_media_set_title (media, value);
- }
- value = g_hash_table_lookup (photosets->data, "description");
- if (value && value[0] != '\0') {
- grl_media_set_description (media, value);
- }
-
- bs->callback (bs->source,
- bs->browse_id,
- media,
- count,
- bs->user_data,
- NULL);
- photosets = g_list_next (photosets);
- }
-}
-
-static void
-photosetsphotos_cb (GFlickr *f, GList *photolist, gpointer user_data)
-{
- GrlMedia *media;
- OperationData *od = (OperationData *) user_data;
- gchar *media_type;
-
- /* Go to offset element */
- photolist = g_list_nth (photolist, od->offset);
-
- /* No more elements can be sent */
- if (!photolist) {
- od->callback (od->source,
- od->operation_id,
- NULL,
- 0,
- od->user_data,
- NULL);
- return;
- }
-
- while (photolist && od->count) {
- media_type = g_hash_table_lookup (photolist->data, "photo_media");
- if (strcmp (media_type, "photo") == 0) {
- media = grl_media_image_new ();
- } else {
- media = grl_media_video_new ();
- }
-
- update_media (media, photolist->data);
- od->callback (od->source,
- od->operation_id,
- media,
- od->count == 1? 0: -1,
- od->user_data,
- NULL);
- photolist = g_list_next (photolist);
- od->count--;
- }
-
- /* Get more elements */
- if (od->count) {
- od->offset = 0;
- od->page++;
- g_flickr_photosets_getPhotos (f, od->text, od->page, photosetsphotos_cb, od);
- } else {
- g_slice_free (OperationData, od);
- }
-}
-
-static void
-gettags_cb (GFlickr *f, GList *taglist, gpointer user_data)
-{
- GrlMedia *media;
- GrlMediaSourceBrowseSpec *bs = (GrlMediaSourceBrowseSpec *) user_data;
- gint count;
-
- /* Go to offset element */
- taglist = g_list_nth (taglist, bs->skip);
-
- /* No more elements can be sent */
- if (!taglist) {
- bs->callback (bs->source,
- bs->browse_id,
- NULL,
- 0,
- bs->user_data,
- NULL);
- return;
- }
-
- /* Send data */
- count = g_list_length (taglist);
- while (taglist) {
- count--;
- media = grl_media_box_new ();
- grl_media_set_id (media, taglist->data);
- grl_media_set_title (media, taglist->data);
- bs->callback (bs->source,
- bs->browse_id,
- media,
- count,
- bs->user_data,
- NULL);
- taglist = g_list_next (taglist);
- }
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_flickr_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_AUTHOR,
- GRL_METADATA_KEY_DATE,
- GRL_METADATA_KEY_DESCRIPTION,
- GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_THUMBNAIL,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_URL,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_flickr_source_public_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- GFlickr *f = GRL_FLICKR_SOURCE (source)->priv->flickr;
- const gchar *container_id;
- guint per_page;
- gint request_size;
-
- container_id = grl_media_get_id (bs->container);
-
- if (!container_id) {
- /* Get hot tags list. List is limited up to HOTLIST_MAX tags */
- if (bs->skip >= HOTLIST_MAX) {
- bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, NULL);
- } else {
- request_size = CLAMP (bs->skip + bs->count, 1, HOTLIST_MAX);
- g_flickr_tags_getHotList (f, request_size, gettags_cb, bs);
- }
- } else {
- OperationData *od = g_slice_new (OperationData);
-
- grl_paging_translate (bs->skip,
- bs->count,
- SEARCH_MAX,
- &per_page,
- &(od->page),
- &(od->offset));
- g_flickr_set_per_page (f, per_page);
-
- od->source = bs->source;
- od->callback = bs->callback;
- od->user_id = GRL_FLICKR_SOURCE (source)->priv->user_id;
- od->tags = (gchar *) container_id;
- od->text = NULL;
- od->user_data = bs->user_data;
- od->count = bs->count;
- od->operation_id = bs->browse_id;
- g_flickr_photos_search (f,
- od->user_id,
- NULL,
- od->tags,
- od->page,
- search_cb,
- od);
- }
-}
-
-static void
-grl_flickr_source_personal_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- GFlickr *f = GRL_FLICKR_SOURCE (source)->priv->flickr;
- OperationData *od;
- const gchar *container_id;
- guint per_page;
-
- container_id = grl_media_get_id (bs->container);
-
- if (!container_id) {
- /* Get photoset */
- g_flickr_photosets_getList (f, NULL, photosetslist_cb, bs);
- } else {
- od = g_slice_new (OperationData);
-
- /* Compute items per page and page offset */
- grl_paging_translate (bs->skip,
- bs->count,
- SEARCH_MAX,
- &per_page,
- &(od->page),
- &(od->offset));
- g_flickr_set_per_page (f, per_page);
- od->source = bs->source;
- od->callback = bs->callback;
- od->tags = NULL;
- od->text = (gchar *) container_id;
- od->user_data = bs->user_data;
- od->count = bs->count;
- od->operation_id = bs->browse_id;
-
- g_flickr_photosets_getPhotos (f, container_id, od->page, photosetsphotos_cb, od);
- }
-}
-
-static void
-grl_flickr_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- if (GRL_FLICKR_SOURCE (source)->priv->user_id) {
- grl_flickr_source_personal_browse (source, bs);
- } else {
- grl_flickr_source_public_browse (source, bs);
- }
-}
-
-static void
-grl_flickr_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- const gchar *id;
-
- if (!ms->media || (id = grl_media_get_id (ms->media)) == NULL) {
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- return;
- }
-
- g_flickr_photos_getInfo (GRL_FLICKR_SOURCE (source)->priv->flickr,
- atol (id),
- getInfo_cb,
- ms);
-}
-
-static void
-grl_flickr_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss)
-{
- GFlickr *f = GRL_FLICKR_SOURCE (source)->priv->flickr;
- guint per_page;
- OperationData *od = g_slice_new (OperationData);
-
- /* Compute items per page and page offset */
- grl_paging_translate (ss->skip,
- ss->count,
- SEARCH_MAX,
- &per_page,
- &(od->page),
- &(od->offset));
- g_flickr_set_per_page (f, per_page);
-
- od->source = ss->source;
- od->callback = ss->callback;
- od->user_id = GRL_FLICKR_SOURCE (source)->priv->user_id;
- od->tags = NULL;
- od->text = ss->text;
- od->user_data = ss->user_data;
- od->count = ss->count;
- od->operation_id = ss->search_id;
-
- if (od->user_id || od->text) {
- g_flickr_photos_search (f,
- od->user_id,
- ss->text,
- NULL,
- od->page,
- search_cb,
- od);
- } else {
- g_flickr_photos_getRecent (f,
- od->page,
- search_cb,
- od);
- }
-}
diff --git a/src/flickr/grl-flickr.h b/src/flickr/grl-flickr.h
deleted file mode 100644
index af1a843..0000000
--- a/src/flickr/grl-flickr.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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_FLICKR_SOURCE_H_
-#define _GRL_FLICKR_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_FLICKR_SOURCE_TYPE \
- (grl_flickr_source_get_type ())
-
-#define GRL_FLICKR_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_FLICKR_SOURCE_TYPE, \
- GrlFlickrSource))
-
-#define GRL_IS_FLICKR_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_FLICKR_SOURCE_TYPE))
-
-#define GRL_FLICKR_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_FLICKR_SOURCE_TYPE, \
- GrlFlickrSourceClass))
-
-#define GRL_IS_FLICKR_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_FLICKR_SOURCE_TYPE))
-
-#define GRL_FLICKR_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_FLICKR_SOURCE_TYPE, \
- GrlFlickrSourceClass))
-
-/* plugin's log domain */
-GRL_LOG_DOMAIN_EXTERN(flickr_log_domain);
-
-typedef struct _GrlFlickrSource GrlFlickrSource;
-typedef struct _GrlFlickrSourcePrivate GrlFlickrSourcePrivate;
-
-struct _GrlFlickrSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlFlickrSourcePrivate *priv;
-};
-
-typedef struct _GrlFlickrSourceClass GrlFlickrSourceClass;
-
-struct _GrlFlickrSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_flickr_source_get_type (void);
-
-#endif /* _GRL_FLICKR_SOURCE_H_ */
diff --git a/src/flickr/grl-flickr.xml b/src/flickr/grl-flickr.xml
deleted file mode 100644
index 4b47f05..0000000
--- a/src/flickr/grl-flickr.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Flickr</name>
- <description>A plugin for browsing and searching Flickr photos</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/gravatar/Makefile.am b/src/gravatar/Makefile.am
deleted file mode 100644
index 391795e..0000000
--- a/src/gravatar/Makefile.am
+++ /dev/null
@@ -1,32 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Juan A. Suarez Romero <jasuarez igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlgravatar.la
-
-libgrlgravatar_la_CFLAGS = \
- $(DEPS_CFLAGS)
-
-libgrlgravatar_la_LIBADD = \
- $(DEPS_LIBS)
-
-libgrlgravatar_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlgravatar_la_SOURCES = grl-gravatar.c grl-gravatar.h
-
-libdir=$(GRL_PLUGINS_DIR)
-gravatarxmldir = $(GRL_PLUGINS_CONF_DIR)
-gravatarxml_DATA = $(GRAVATAR_PLUGIN_ID).xml
-
-EXTRA_DIST = $(gravatarxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/gravatar/grl-gravatar.c b/src/gravatar/grl-gravatar.c
deleted file mode 100644
index 41d9fa5..0000000
--- a/src/gravatar/grl-gravatar.c
+++ /dev/null
@@ -1,301 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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 "grl-gravatar.h"
-
-/* ---------- Logging ---------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT gravatar_log_domain
-GRL_LOG_DOMAIN_STATIC(gravatar_log_domain);
-
-/* -------- Gravatar API -------- */
-
-#define GRAVATAR_URL "http://www.gravatar.com/avatar/%s.jpg"
-
-/* ------- Pluging Info -------- */
-
-#define PLUGIN_ID GRAVATAR_PLUGIN_ID
-
-#define SOURCE_ID PLUGIN_ID
-#define SOURCE_NAME "Avatar provider from Gravatar"
-#define SOURCE_DESC "A plugin to get avatars for artist and author fields"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-
-static GrlGravatarSource *grl_gravatar_source_new (void);
-
-static void grl_gravatar_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs);
-
-static const GList *grl_gravatar_source_supported_keys (GrlMetadataSource *source);
-
-static const GList *grl_gravatar_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id);
-
-static GrlKeyID register_gravatar_key (GrlPluginRegistry *registry,
- const gchar *name,
- const gchar *nick,
- const gchar *blurb);
-
-gboolean grl_gravatar_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-GrlKeyID GRL_METADATA_KEY_ARTIST_AVATAR = NULL;
-GrlKeyID GRL_METADATA_KEY_AUTHOR_AVATAR = NULL;
-
-/* =================== Gravatar Plugin =============== */
-
-gboolean
-grl_gravatar_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (gravatar_log_domain, "gravatar");
-
- GRL_DEBUG ("grl_gravatar_source_plugin_init");
-
- /* Register keys */
- GRL_METADATA_KEY_ARTIST_AVATAR =
- register_gravatar_key (registry,
- "artist-avatar",
- "ArtistAvatar",
- "Avatar for the artist");
-
- GRL_METADATA_KEY_AUTHOR_AVATAR =
- register_gravatar_key (registry,
- "author-avatar",
- "AuthorAvatar",
- "Avatar for the author");
- if (!GRL_METADATA_KEY_ARTIST_AVATAR &&
- !GRL_METADATA_KEY_AUTHOR_AVATAR) {
- GRL_WARNING ("Unable to register \"autor-avatar\" nor \"artist-avatar\"");
- return FALSE;
- }
-
- GrlGravatarSource *source = grl_gravatar_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_gravatar_source_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Gravatar GObject ================ */
-
-static GrlGravatarSource *
-grl_gravatar_source_new (void)
-{
- GRL_DEBUG ("grl_gravatar_source_new");
- return g_object_new (GRL_GRAVATAR_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_gravatar_source_class_init (GrlGravatarSourceClass * klass)
-{
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- metadata_class->supported_keys = grl_gravatar_source_supported_keys;
- metadata_class->key_depends = grl_gravatar_source_key_depends;
- metadata_class->resolve = grl_gravatar_source_resolve;
-}
-
-static void
-grl_gravatar_source_init (GrlGravatarSource *source)
-{
-}
-
-G_DEFINE_TYPE (GrlGravatarSource,
- grl_gravatar_source,
- GRL_TYPE_METADATA_SOURCE);
-
-/* ======================= Utilities ==================== */
-
-static GrlKeyID
-register_gravatar_key (GrlPluginRegistry *registry,
- const gchar *name,
- const gchar *nick,
- const gchar *blurb)
-{
- GParamSpec *spec;
- GrlKeyID key;
-
- spec = g_param_spec_string (name,
- nick,
- blurb,
- NULL,
- G_PARAM_READWRITE);
-
- key = grl_plugin_registry_register_metadata_key (registry, spec, NULL);
-
- /* If key was not registered, could be that it is already registered. If so,
- check if type is the expected one, and reuse it */
- if (!key) {
- g_param_spec_unref (spec);
- key = grl_plugin_registry_lookup_metadata_key (registry, name);
- if (!key || GRL_METADATA_KEY_GET_TYPE (key) != G_TYPE_STRING) {
- key = NULL;
- }
- }
-
- return key;
-}
-
-static gchar *
-get_avatar (const gchar *field) {
- GMatchInfo *match_info = NULL;
- gchar *avatar = NULL;
- gchar *email;
- gchar *email_hash;
- gchar *lowercased_field;
- static GRegex *email_regex = NULL;
-
- if (!field) {
- return NULL;
- }
-
- lowercased_field = g_utf8_strdown (field, -1);
-
- if (!email_regex) {
- email_regex = g_regex_new ("[\\w-]+@([\\w-]+\\.)+[\\w-]+", G_REGEX_OPTIMIZE, 0, NULL);
- }
-
- if (g_regex_match (email_regex, lowercased_field, 0, &match_info)) {
- email = g_match_info_fetch (match_info, 0);
- g_match_info_free (match_info);
- email_hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, email, -1);
- avatar = g_strdup_printf (GRAVATAR_URL, email_hash);
- g_free (email);
- g_free (email_hash);
- }
-
- return avatar;
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_gravatar_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
-
- if (!keys) {
- if (GRL_METADATA_KEY_ARTIST_AVATAR) {
- keys = g_list_prepend (keys, GRL_METADATA_KEY_ARTIST_AVATAR);
- }
- if (GRL_METADATA_KEY_AUTHOR_AVATAR) {
- keys =g_list_prepend (keys, GRL_METADATA_KEY_AUTHOR_AVATAR);
- }
- }
-
- return keys;
-}
-
-static const GList *
-grl_gravatar_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id)
-{
- static GList *artist_avatar_deps = NULL;
- static GList *author_avatar_deps = NULL;
-
- if (!artist_avatar_deps) {
- artist_avatar_deps = grl_metadata_key_list_new (GRL_METADATA_KEY_ARTIST,
- NULL);
- }
-
- if (!author_avatar_deps) {
- author_avatar_deps = grl_metadata_key_list_new (GRL_METADATA_KEY_AUTHOR,
- NULL);
- }
-
- if (key_id == GRL_METADATA_KEY_ARTIST_AVATAR) {
- return artist_avatar_deps;
- } else if (key_id == GRL_METADATA_KEY_AUTHOR_AVATAR) {
- return author_avatar_deps;
- } else {
- return NULL;
- }
-}
-
-static void
-grl_gravatar_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs)
-{
- gboolean artist_avatar_required = FALSE;
- gboolean author_avatar_required = FALSE;
- gchar *avatar_url;
-
- GRL_DEBUG ("grl_gravatar_source_resolve");
-
- GList *iter;
-
- /* Check that albumart is requested */
- iter = rs->keys;
- while (iter && (!artist_avatar_required || !author_avatar_required)) {
- if (iter->data == GRL_METADATA_KEY_ARTIST_AVATAR) {
- artist_avatar_required = TRUE;
- } else if (iter->data == GRL_METADATA_KEY_AUTHOR_AVATAR) {
- author_avatar_required = TRUE;
- }
- iter = g_list_next (iter);
- }
-
- if (artist_avatar_required) {
- avatar_url = get_avatar (grl_data_get_string (GRL_DATA (rs->media),
- GRL_METADATA_KEY_ARTIST));
- if (avatar_url) {
- grl_data_set_string (GRL_DATA (rs->media),
- GRL_METADATA_KEY_ARTIST_AVATAR,
- avatar_url);
- g_free (avatar_url);
- }
- }
-
- if (author_avatar_required) {
- avatar_url = get_avatar (grl_data_get_string (GRL_DATA (rs->media),
- GRL_METADATA_KEY_AUTHOR));
- if (avatar_url) {
- grl_data_set_string (GRL_DATA (rs->media),
- GRL_METADATA_KEY_AUTHOR_AVATAR,
- avatar_url);
- g_free (avatar_url);
- }
- }
-
- rs->callback (source, rs->media, rs->user_data, NULL);
-}
diff --git a/src/gravatar/grl-gravatar.h b/src/gravatar/grl-gravatar.h
deleted file mode 100644
index 644efdb..0000000
--- a/src/gravatar/grl-gravatar.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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_GRAVATAR_H_
-#define _GRL_GRAVATAR_H_
-
-#include <grilo.h>
-
-#define GRL_GRAVATAR_SOURCE_TYPE \
- (grl_gravatar_source_get_type ())
-
-#define GRL_GRAVATAR_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_GRAVATAR_SOURCE_TYPE, \
- GrlGravatarSource))
-
-#define GRL_IS_GRAVATAR_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_GRAVATAR_SOURCE_TYPE))
-
-#define GRL_GRAVATAR_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_GRAVATAR_SOURCE_TYPE, \
- GrlGravatarSourceClass))
-
-#define GRL_IS_GRAVATAR_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_GRAVATAR_SOURCE_TYPE))
-
-#define GRL_GRAVATAR_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_GRAVATAR_SOURCE_TYPE, \
- GrlGravatarSourceClass))
-
-typedef struct _GrlGravatarSource GrlGravatarSource;
-
-struct _GrlGravatarSource {
-
- GrlMetadataSource parent;
-
-};
-
-typedef struct _GrlGravatarSourceClass GrlGravatarSourceClass;
-
-struct _GrlGravatarSourceClass {
-
- GrlMetadataSourceClass parent_class;
-
-};
-
-GType grl_gravatar_source_get_type (void);
-
-#endif /* _GRL_GRAVATAR_H_ */
diff --git a/src/gravatar/grl-gravatar.xml b/src/gravatar/grl-gravatar.xml
deleted file mode 100644
index 58f60d1..0000000
--- a/src/gravatar/grl-gravatar.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Avatar provider from Gravatar</name>
- <description>A plugin to get avatars for artist and author fields</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/jamendo/Makefile.am b/src/jamendo/Makefile.am
deleted file mode 100644
index 3232ac2..0000000
--- a/src/jamendo/Makefile.am
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Juan A. Suarez Romero <jasuarez igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrljamendo.la
-
-libgrljamendo_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GRLNET_CFLAGS) \
- $(XML_CFLAGS)
-
-libgrljamendo_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GRLNET_LIBS) \
- $(XML_LIBS)
-
-libgrljamendo_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrljamendo_la_SOURCES = \
- grl-jamendo.c \
- grl-jamendo.h
-
-libdir = $(GRL_PLUGINS_DIR)
-jamendoxmldir = $(GRL_PLUGINS_CONF_DIR)
-jamendoxml_DATA = $(JAMENDO_PLUGIN_ID).xml
-
-EXTRA_DIST = $(jamendoxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/jamendo/TODO b/src/jamendo/TODO
deleted file mode 100644
index 2571947..0000000
--- a/src/jamendo/TODO
+++ /dev/null
@@ -1,30 +0,0 @@
-Get global database and work locally
-====================================
-Actually all queries are on-line. But the whole database can be retrieved and
-stored in local, so all operation can be performed off-line.
-
-Doing it requires to implement the search in local. We should explore if either
-moving to this approach or even creating a new derivative plugin that follows
-this off-line approach worths it.
-
-
-Implement childcount
-====================
-Implementing childcount would require, for each result doing another search and
-retrieve all children, counting them.
-
-
-Limit search-rate
-=================
-Jamendo terms of use states that applications should be limited to 1 query per
-second. Some kind of control should be needed in the plugin.
-
-
-Limit requested elements
-========================
-User can request any arbitrary number of elements, and this request is propagated to Jamendo.
-
-Nevertheless, it would be better to break the request in smaller chunks, so
-though user requests, for instance, 1000 albums, we do the query in steps of 25
-elements.
-
diff --git a/src/jamendo/grl-jamendo.c b/src/jamendo/grl-jamendo.c
deleted file mode 100644
index f6ba5c6..0000000
--- a/src/jamendo/grl-jamendo.c
+++ /dev/null
@@ -1,1365 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
-#include <net/grl-net.h>
-#include <libxml/parser.h>
-#include <libxml/xmlmemory.h>
-#include <string.h>
-
-#include "grl-jamendo.h"
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT jamendo_log_domain
-GRL_LOG_DOMAIN_STATIC(jamendo_log_domain);
-
-#define JAMENDO_ID_SEP "/"
-#define JAMENDO_ROOT_NAME "Jamendo"
-
-#define MAX_ELEMENTS 100
-
-/* ------- Categories ------- */
-
-#define JAMENDO_ARTIST "artist"
-#define JAMENDO_ALBUM "album"
-#define JAMENDO_TRACK "track"
-
-/* ---- Jamendo Web API ---- */
-
-#define JAMENDO_BASE_ENTRY "http://api.jamendo.com/get2"
-#define JAMENDO_FORMAT "xml"
-#define JAMENDO_RANGE "n=%u&pn=%u"
-
-#define JAMENDO_ARTIST_ENTRY JAMENDO_BASE_ENTRY "/%s/" JAMENDO_ARTIST "/" JAMENDO_FORMAT
-
-#define JAMENDO_ALBUM_ENTRY JAMENDO_BASE_ENTRY "/%s/" JAMENDO_ALBUM "/" JAMENDO_FORMAT \
- "/" JAMENDO_ALBUM "_" JAMENDO_ARTIST
-
-#define JAMENDO_TRACK_ENTRY JAMENDO_BASE_ENTRY "/%s/" JAMENDO_TRACK "/" JAMENDO_FORMAT \
- "/" JAMENDO_ALBUM "_" JAMENDO_ARTIST "+" JAMENDO_TRACK "_" JAMENDO_ALBUM
-
-#define JAMENDO_GET_ARTISTS JAMENDO_ARTIST_ENTRY "/?" JAMENDO_RANGE
-#define JAMENDO_GET_ALBUMS JAMENDO_ALBUM_ENTRY "/?" JAMENDO_RANGE
-#define JAMENDO_GET_TRACKS JAMENDO_TRACK_ENTRY "/?" JAMENDO_RANGE
-
-#define JAMENDO_GET_ALBUMS_FROM_ARTIST JAMENDO_ALBUM_ENTRY "/?" JAMENDO_RANGE "&artist_id=%s"
-#define JAMENDO_GET_TRACKS_FROM_ALBUM JAMENDO_TRACK_ENTRY "/?" JAMENDO_RANGE "&album_id=%s"
-#define JAMENDO_GET_ARTIST JAMENDO_ARTIST_ENTRY "/?id=%s"
-
-#define JAMENDO_GET_ALBUM JAMENDO_ALBUM_ENTRY "/?id=%s"
-#define JAMENDO_GET_TRACK JAMENDO_TRACK_ENTRY "/?id=%s"
-
-#define JAMENDO_SEARCH_ARTIST JAMENDO_ARTIST_ENTRY "/?" JAMENDO_RANGE "&searchquery=%s"
-#define JAMENDO_SEARCH_ALBUM JAMENDO_ALBUM_ENTRY "/?" JAMENDO_RANGE "&searchquery=%s"
-#define JAMENDO_SEARCH_TRACK JAMENDO_TRACK_ENTRY "/?" JAMENDO_RANGE "&searchquery=%s"
-#define JAMENDO_SEARCH_ALL JAMENDO_TRACK_ENTRY "/?" JAMENDO_RANGE
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID JAMENDO_PLUGIN_ID
-
-#define SOURCE_ID "grl-jamendo"
-#define SOURCE_NAME "Jamendo"
-#define SOURCE_DESC "A source for browsing and searching Jamendo videos"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-enum {
- METADATA,
- BROWSE,
- QUERY,
- SEARCH
-};
-
-typedef enum {
- JAMENDO_ARTIST_CAT = 1,
- JAMENDO_ALBUM_CAT,
- JAMENDO_FEEDS_CAT,
- JAMENDO_TRACK_CAT,
-} JamendoCategory;
-
-typedef struct {
- JamendoCategory category;
- gchar *id;
- gchar *artist_name;
- gchar *artist_genre;
- gchar *artist_url;
- gchar *artist_image;
- gchar *album_name;
- gchar *album_genre;
- gchar *album_url;
- gchar *album_duration;
- gchar *album_image;
- gchar *track_name;
- gchar *track_url;
- gchar *track_stream;
- gchar *track_duration;
- gchar *feed_name;
-} Entry;
-
-typedef struct {
- gint type;
- union {
- GrlMediaSourceBrowseSpec *bs;
- GrlMediaSourceQuerySpec *qs;
- GrlMediaSourceMetadataSpec *ms;
- GrlMediaSourceSearchSpec *ss;
- } spec;
- xmlNodePtr node;
- xmlDocPtr doc;
- guint total_results;
- guint index;
- guint offset;
- gboolean cancelled;
-} XmlParseEntries;
-
-struct Feeds {
- gchar *name;
- JamendoCategory cat;
- gchar *url;
-} feeds[] = {
- { "Albums of the week", JAMENDO_ALBUM_CAT,
- JAMENDO_GET_ALBUMS "&order=ratingweek_desc", },
- { "Tracks of the week", JAMENDO_TRACK_CAT,
- JAMENDO_GET_TRACKS "&order=ratingweek_desc", },
- { "New releases", JAMENDO_TRACK_CAT,
- JAMENDO_GET_TRACKS "&order=releasedate_desc", },
- { "Top artists", JAMENDO_ARTIST_CAT,
- JAMENDO_GET_ARTISTS "&order=rating_desc", },
- { "Top albums", JAMENDO_ALBUM_CAT,
- JAMENDO_GET_ALBUMS "&order=rating_desc", },
- { "Top tracks", JAMENDO_TRACK_CAT,
- JAMENDO_GET_TRACKS "&order=rating_desc", },
-};
-
-struct _GrlJamendoSourcePriv {
- GrlNetWc *wc;
- GCancellable *cancellable;
-};
-
-#define GRL_JAMENDO_SOURCE_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_JAMENDO_SOURCE_TYPE, \
- GrlJamendoSourcePriv))
-
-static GrlJamendoSource *grl_jamendo_source_new (void);
-
-gboolean grl_jamendo_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-static const GList *grl_jamendo_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_jamendo_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms);
-
-static void grl_jamendo_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-
-static void grl_jamendo_source_query (GrlMediaSource *source,
- GrlMediaSourceQuerySpec *qs);
-
-static void grl_jamendo_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-
-static void grl_jamendo_source_cancel (GrlMediaSource *source,
- guint operation_id);
-
-/* =================== Jamendo Plugin =============== */
-
-gboolean
-grl_jamendo_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (jamendo_log_domain, "jamendo");
-
- GRL_DEBUG ("jamendo_plugin_init");
-
- GrlJamendoSource *source = grl_jamendo_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_jamendo_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Jamendo GObject ================ */
-
-static GrlJamendoSource *
-grl_jamendo_source_new (void)
-{
- GRL_DEBUG ("grl_jamendo_source_new");
- return g_object_new (GRL_JAMENDO_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-G_DEFINE_TYPE (GrlJamendoSource, grl_jamendo_source, GRL_TYPE_MEDIA_SOURCE);
-
-static void
-grl_jamendo_source_finalize (GObject *object)
-{
- GrlJamendoSource *self;
-
- self = GRL_JAMENDO_SOURCE (object);
- if (self->priv->wc)
- g_object_unref (self->priv->wc);
-
- if (self->priv->cancellable
- && G_IS_CANCELLABLE (self->priv->cancellable))
- g_object_unref (self->priv->cancellable);
-
- G_OBJECT_CLASS (grl_jamendo_source_parent_class)->finalize (object);
-}
-
-static void
-grl_jamendo_source_class_init (GrlJamendoSourceClass * klass)
-{
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- GObjectClass *g_class = G_OBJECT_CLASS (klass);
- source_class->metadata = grl_jamendo_source_metadata;
- source_class->browse = grl_jamendo_source_browse;
- source_class->query = grl_jamendo_source_query;
- source_class->search = grl_jamendo_source_search;
- source_class->cancel = grl_jamendo_source_cancel;
- metadata_class->supported_keys = grl_jamendo_source_supported_keys;
- g_class->finalize = grl_jamendo_source_finalize;
-
- g_type_class_add_private (klass, sizeof (GrlJamendoSourcePriv));
-}
-
-static void
-grl_jamendo_source_init (GrlJamendoSource *source)
-{
- source->priv = GRL_JAMENDO_SOURCE_GET_PRIVATE (source);
-
- /* If we try to get too much elements in a single step, Jamendo might return
- nothing. So limit the maximum amount of elements in each query */
- grl_media_source_set_auto_split_threshold (GRL_MEDIA_SOURCE (source),
- MAX_ELEMENTS);
-}
-
-/* ======================= Utilities ==================== */
-
-#if 0
-static void
-print_entry (Entry *entry)
-{
- g_print ("Entry Information:\n");
- g_print (" ID: %s\n", entry->id);
- g_print (" Artist Name: %s\n", entry->artist_name);
- g_print (" Artist Genre: %s\n", entry->artist_genre);
- g_print (" Artist URL: %s\n", entry->artist_url);
- g_print (" Artist Image: %s\n", entry->artist_image);
- g_print (" Album Name: %s\n", entry->album_name);
- g_print (" Album Genre: %s\n", entry->album_genre);
- g_print (" Album URL: %s\n", entry->album_url);
- g_print ("Album Duration: %s\n", entry->album_duration);
- g_print (" Album Image: %s\n", entry->album_image);
- g_print (" Track Name: %s\n", entry->track_name);
- g_print (" Track URL: %s\n", entry->track_url);
- g_print (" Track Stream: %s\n", entry->track_stream);
- g_print ("Track Duration: %s\n", entry->track_duration);
- g_print (" Feed Name: %s\n", entry->feed_name);
-}
-#endif
-
-static void
-free_entry (Entry *entry)
-{
- g_free (entry->id);
- g_free (entry->artist_name);
- g_free (entry->artist_genre);
- g_free (entry->artist_url);
- g_free (entry->artist_image);
- g_free (entry->album_name);
- g_free (entry->album_genre);
- g_free (entry->album_url);
- g_free (entry->album_duration);
- g_free (entry->album_image);
- g_free (entry->track_name);
- g_free (entry->track_url);
- g_free (entry->track_stream);
- g_free (entry->track_duration);
- g_free (entry->feed_name);
- g_slice_free (Entry, entry);
-}
-
-static gint
-xml_count_children (xmlNodePtr node)
-{
-#if (LIBXML2_VERSION >= 20700)
- return xmlChildElementCount (node);
-#else
- gint nchildren = 0;
- xmlNodePtr i = node->xmlChildrenNode;
-
- while (i) {
- nchildren++;
- i = i->next;
- }
-
- return nchildren;
-#endif
-}
-
-static void
-xml_parse_result (const gchar *str, GError **error, XmlParseEntries *xpe)
-{
- xmlDocPtr doc;
- xmlNodePtr node;
- gint child_nodes = 0;
-
- doc = xmlReadMemory (str, strlen (str), NULL, NULL,
- XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- if (!doc) {
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Failed to parse Jamendo's response");
- goto free_resources;
- }
-
- node = xmlDocGetRootElement (doc);
- if (!node) {
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Empty response from Jamendo");
- goto free_resources;
- }
-
- if (xmlStrcmp (node->name, (const xmlChar *) "data")) {
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Unexpected response from Jamendo: no data");
- goto free_resources;
- }
-
- child_nodes = xml_count_children (node);
- node = node->xmlChildrenNode;
-
- /* Skip offset */
- while (node && xpe->offset > 0) {
- node = node->next;
- child_nodes--;
- xpe->offset--;
- }
-
- xpe->node = node;
- xpe->doc = doc;
- xpe->total_results = child_nodes;
-
- return;
-
- free_resources:
- xmlFreeDoc (doc);
-}
-
-static Entry *
-xml_parse_entry (xmlDocPtr doc, xmlNodePtr entry)
-{
- xmlNodePtr node;
- xmlNs *ns;
- Entry *data = g_slice_new0 (Entry);
-
- if (strcmp ((gchar *) entry->name, JAMENDO_ARTIST) == 0) {
- data->category = JAMENDO_ARTIST_CAT;
- } else if (strcmp ((gchar *) entry->name, JAMENDO_ALBUM) == 0) {
- data->category = JAMENDO_ALBUM_CAT;
- } else if (strcmp ((gchar *) entry->name, JAMENDO_TRACK) == 0) {
- data->category = JAMENDO_TRACK_CAT;
- } else {
- g_return_val_if_reached (NULL);
- }
-
- node = entry->xmlChildrenNode;
-
- while (node) {
- ns = node->ns;
-
- if (!xmlStrcmp (node->name, (const xmlChar *) "id")) {
- data->id =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "artist_name")) {
- data->artist_name =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_name")) {
- data->album_name =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "artist_genre")) {
- data->artist_genre =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "artist_url")) {
- data->artist_url =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "artist_image")) {
- data->artist_image =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_genre")) {
- data->album_genre =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_url")) {
- data->album_url =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_duration")) {
- data->album_duration =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_image")) {
- data->album_image =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "track_name")) {
- data->track_name =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "track_url")) {
- data->track_url =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "track_stream")) {
- data->track_stream =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
-
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "track_duration")) {
- data->track_duration =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
- }
-
- node = node->next;
- }
-
- return data;
-}
-
-static void
-update_media_from_entry (GrlMedia *media, const Entry *entry)
-{
- gchar *id;
-
- if (entry->id) {
- id = g_strdup_printf ("%d/%s", entry->category, entry->id);
- } else {
- id = g_strdup_printf ("%d", entry->category);
- }
-
- /* Common fields */
- grl_media_set_id (media, id);
- g_free (id);
-
- if (entry->artist_name) {
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_ARTIST,
- entry->artist_name);
- }
-
- if (entry->album_name) {
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_ALBUM,
- entry->album_name);
- }
-
- /* Fields for artist */
- if (entry->category == JAMENDO_ARTIST_CAT) {
- if (entry->artist_name) {
- grl_media_set_title (media, entry->artist_name);
- }
-
- if (entry->artist_genre) {
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_GENRE,
- entry->artist_genre);
- }
-
- if (entry->artist_url) {
- grl_media_set_site (media, entry->artist_url);
- }
-
- if (entry->artist_image) {
- grl_media_set_thumbnail (media, entry->artist_image);
- }
-
- /* Fields for album */
- } else if (entry->category == JAMENDO_ALBUM_CAT) {
- if (entry->album_name) {
- grl_media_set_title (media, entry->album_name);
- }
-
- if (entry->album_genre) {
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_GENRE,
- entry->album_genre);
- }
-
- if (entry->album_url) {
- grl_media_set_site (media, entry->album_url);
- }
-
- if (entry->album_image) {
- grl_media_set_thumbnail (media, entry->album_image);
- }
-
- if (entry->album_duration) {
- grl_media_set_duration (media, atoi (entry->album_duration));
- }
-
- /* Fields for track */
- } else if (entry->category == JAMENDO_TRACK_CAT) {
- if (entry->track_name) {
- grl_media_set_title (media, entry->track_name);
- }
-
- if (entry->album_genre) {
- grl_media_audio_set_genre (GRL_MEDIA_AUDIO (media),
- entry->album_genre);
- }
-
- if (entry->track_url) {
- grl_media_set_site (media, entry->track_url);
- }
-
- if (entry->album_image) {
- grl_media_set_thumbnail (media, entry->album_image);
- }
-
- if (entry->track_stream) {
- grl_media_set_url (media, entry->track_stream);
- }
-
- if (entry->track_duration) {
- grl_media_set_duration (media, atoi (entry->track_duration));
- }
- } else if (entry->category == JAMENDO_FEEDS_CAT) {
- if (entry->feed_name) {
- grl_media_set_title (media, entry->feed_name);
- }
- }
-}
-
-static gboolean
-xml_parse_entries_idle (gpointer user_data)
-{
- XmlParseEntries *xpe = (XmlParseEntries *) user_data;
- gboolean parse_more;
- GrlMedia *media = NULL;
- Entry *entry;
- gint remaining = 0;
-
- GRL_DEBUG ("xml_parse_entries_idle");
-
- parse_more = (xpe->cancelled == FALSE && xpe->node);
-
- if (parse_more) {
- entry = xml_parse_entry (xpe->doc, xpe->node);
- if (entry->category == JAMENDO_TRACK_CAT) {
- media = grl_media_audio_new ();
- } else {
- media = grl_media_box_new ();
- }
-
- update_media_from_entry (media, entry);
- free_entry (entry);
-
- xpe->index++;
- xpe->node = xpe->node->next;
- remaining = xpe->total_results - xpe->index;
- }
-
- if (parse_more || xpe->cancelled) {
- switch (xpe->type) {
- case BROWSE:
- xpe->spec.bs->callback (xpe->spec.bs->source,
- xpe->spec.bs->browse_id,
- media,
- remaining,
- xpe->spec.bs->user_data,
- NULL);
- break;
- case QUERY:
- xpe->spec.qs->callback (xpe->spec.qs->source,
- xpe->spec.qs->query_id,
- media,
- remaining,
- xpe->spec.qs->user_data,
- NULL);
- break;
- case SEARCH:
- xpe->spec.ss->callback (xpe->spec.ss->source,
- xpe->spec.ss->search_id,
- media,
- remaining,
- xpe->spec.ss->user_data,
- NULL);
- break;
- }
- }
-
- if (!parse_more) {
- xmlFreeDoc (xpe->doc);
- g_slice_free (XmlParseEntries, xpe);
- }
-
- return parse_more;
-}
-
-static void
-read_done_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- XmlParseEntries *xpe = (XmlParseEntries *) user_data;
- gint error_code = -1;
- GError *wc_error = NULL;
- GError *error = NULL;
- gchar *content = NULL;
- Entry *entry = NULL;
-
- /* Check if operation was cancelled */
- if (xpe->cancelled) {
- goto invoke_cb;
- }
-
- if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
- res,
- &content,
- NULL,
- &wc_error)) {
- switch (xpe->type) {
- case METADATA:
- error_code = GRL_CORE_ERROR_METADATA_FAILED;
- break;
- case BROWSE:
- error_code = GRL_CORE_ERROR_BROWSE_FAILED;
- break;
- case QUERY:
- error_code = GRL_CORE_ERROR_QUERY_FAILED;
- break;
- case SEARCH:
- error_code = GRL_CORE_ERROR_SEARCH_FAILED;
- break;
- }
-
- error = g_error_new (GRL_CORE_ERROR,
- error_code,
- "Failed to connect Jamendo: '%s'",
- wc_error->message);
- g_error_free (wc_error);
- goto invoke_cb;
- }
-
- if (content) {
- xml_parse_result (content, &error, xpe);
- } else {
- goto invoke_cb;
- }
-
- if (error) {
- goto invoke_cb;
- }
-
- if (xpe->node) {
- if (xpe->type == METADATA) {
- entry = xml_parse_entry (xpe->doc, xpe->node);
- xmlFreeDoc (xpe->doc);
- update_media_from_entry (xpe->spec.ms->media, entry);
- free_entry (entry);
- goto invoke_cb;
- } else {
- g_idle_add (xml_parse_entries_idle, xpe);
- }
- } else {
- if (xpe->type == METADATA) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Unable to get information: '%s'",
- grl_media_get_id (xpe->spec.ms->media));
- }
- goto invoke_cb;
- }
-
- return;
-
- invoke_cb:
- switch (xpe->type) {
- case METADATA:
- xpe->spec.ms->callback (xpe->spec.ms->source,
- xpe->spec.ms->media,
- xpe->spec.ms->user_data,
- error);
- break;
- case BROWSE:
- xpe->spec.bs->callback (xpe->spec.bs->source,
- xpe->spec.bs->browse_id,
- NULL,
- 0,
- xpe->spec.bs->user_data,
- error);
- break;
- case QUERY:
- xpe->spec.qs->callback (xpe->spec.qs->source,
- xpe->spec.qs->query_id,
- NULL,
- 0,
- xpe->spec.qs->user_data,
- error);
- break;
- case SEARCH:
- xpe->spec.ss->callback (xpe->spec.ss->source,
- xpe->spec.ss->search_id,
- NULL,
- 0,
- xpe->spec.ss->user_data,
- error);
- break;
- }
-
- g_slice_free (XmlParseEntries, xpe);
- if (error) {
- g_error_free (error);
- }
-}
-
-static void
-read_url_async (GrlJamendoSource *source,
- const gchar *url,
- gpointer user_data)
-{
- if (!source->priv->wc)
- source->priv->wc = g_object_new (GRL_TYPE_NET_WC, "throttling", 1, NULL);
-
- source->priv->cancellable = g_cancellable_new ();
-
- GRL_DEBUG ("Opening '%s'", url);
- grl_net_wc_request_async (source->priv->wc,
- url,
- source->priv->cancellable,
- read_done_cb,
- user_data);
-}
-
-static void
-update_media_from_root (GrlMedia *media)
-{
- grl_media_set_title (media, JAMENDO_ROOT_NAME);
- grl_media_box_set_childcount (GRL_MEDIA_BOX (media), 3);
-}
-
-static void
-update_media_from_artists (GrlMedia *media)
-{
- Entry entry = {
- .category = JAMENDO_ARTIST_CAT,
- .artist_name = JAMENDO_ARTIST "s",
- };
-
- update_media_from_entry (media, &entry);
-}
-
-static void
-update_media_from_albums (GrlMedia *media)
-{
- Entry entry = {
- .category = JAMENDO_ALBUM_CAT,
- .album_name = JAMENDO_ALBUM "s",
- };
-
- update_media_from_entry (media, &entry);
-}
-
-static void
-update_media_from_feeds (GrlMedia *media)
-{
- Entry entry = {
- .category = JAMENDO_FEEDS_CAT,
- .feed_name = "feeds",
- };
-
- update_media_from_entry (media, &entry);
- grl_media_box_set_childcount (GRL_MEDIA_BOX (media), G_N_ELEMENTS(feeds));
-}
-
-static void
-send_toplevel_categories (GrlMediaSourceBrowseSpec *bs)
-{
- GrlMedia *media;
- gint remaining;
-
- /* Check if all elements must be skipped */
- if (bs->skip > 1 || bs->count == 0) {
- bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, NULL);
- return;
- }
-
- remaining = bs->count;
-
- if (bs->skip == 0) {
- media = grl_media_box_new ();
- update_media_from_artists (media);
- remaining--;
- bs->callback (bs->source, bs->browse_id, media, remaining, bs->user_data, NULL);
- }
-
- if (remaining) {
- media = grl_media_box_new ();
- update_media_from_albums (media);
- bs->callback (bs->source, bs->browse_id, media, remaining, bs->user_data, NULL);
- remaining--;
- }
-
- if (remaining) {
- media = grl_media_box_new ();
- update_media_from_feeds (media);
- bs->callback (bs->source, bs->browse_id, media, 0, bs->user_data, NULL);
- }
-}
-
-static void
-update_media_from_feed (GrlMedia *media, int i)
-{
- char *id;
-
- id = g_strdup_printf("%d/%d", JAMENDO_FEEDS_CAT, i);
- grl_media_set_id (media, id);
- g_free (id);
-
- grl_media_set_title (media, feeds[i].name);
-}
-
-static void
-send_feeds (GrlMediaSourceBrowseSpec *bs)
-{
- int i;
- int remaining;
-
- remaining = MIN (bs->count, G_N_ELEMENTS (feeds));
- for (i = bs->skip; remaining > 0 && i < G_N_ELEMENTS (feeds); i++) {
- GrlMedia *media;
-
- media = grl_media_box_new ();
- update_media_from_feed (media, i);
- remaining--;
- bs->callback (bs->source,
- bs->browse_id,
- media,
- remaining,
- bs->user_data,
- NULL);
- }
-}
-
-static gchar *
-get_jamendo_keys (JamendoCategory category)
-{
- gchar *jamendo_keys = NULL;
- gchar *keys_for_artist = "artist_name+artist_genre+artist_image+artist_url";
- gchar *keys_for_album = "album_name+album_genre+album_image+album_url+album_duration";
- gchar *keys_for_track = "track_name+track_stream+track_url+track_duration";
-
- if (category == JAMENDO_ARTIST_CAT) {
- jamendo_keys = g_strconcat ("id+", keys_for_artist, NULL);
- } else if (category == JAMENDO_ALBUM_CAT) {
- jamendo_keys = g_strconcat ("id+", keys_for_artist,
- "+", keys_for_album,
- NULL);
- } else if (category == JAMENDO_TRACK_CAT) {
- jamendo_keys = g_strconcat ("id+", keys_for_artist,
- "+", keys_for_album,
- "+", keys_for_track,
- NULL);
- }
-
- return jamendo_keys;
-}
-
-static gboolean
-parse_query (const gchar *query, JamendoCategory *category, gchar **term)
-{
- if (!query) {
- return FALSE;
- }
-
- if (g_str_has_prefix (query, JAMENDO_ARTIST "=")) {
- *category = JAMENDO_ARTIST_CAT;
- query += 7;
- } else if (g_str_has_prefix (query, JAMENDO_ALBUM "=")) {
- *category = JAMENDO_ALBUM_CAT;
- query += 6;
- } else if (g_str_has_prefix (query, JAMENDO_TRACK "=")) {
- *category = JAMENDO_TRACK_CAT;
- query += 6;
- } else {
- return FALSE;
- }
-
- *term = g_uri_escape_string (query, NULL, TRUE);
- return TRUE;
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_jamendo_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_ARTIST,
- GRL_METADATA_KEY_ALBUM,
- GRL_METADATA_KEY_GENRE,
- GRL_METADATA_KEY_URL,
- GRL_METADATA_KEY_DURATION,
- GRL_METADATA_KEY_THUMBNAIL,
- GRL_METADATA_KEY_SITE,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_jamendo_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- gchar *url = NULL;
- gchar *jamendo_keys = NULL;
- const gchar *id;
- gchar **id_split = NULL;
- XmlParseEntries *xpe = NULL;
- GError *error = NULL;
- JamendoCategory category;
-
- GRL_DEBUG ("grl_jamendo_source_metadata");
-
- if (!ms->media ||
- !grl_data_key_is_known (GRL_DATA (ms->media),
- GRL_METADATA_KEY_ID)) {
- /* Get info from root */
- if (!ms->media) {
- ms->media = grl_media_box_new ();
- }
- update_media_from_root (ms->media);
- } else {
- id = grl_media_get_id (ms->media);
- id_split = g_strsplit (id, JAMENDO_ID_SEP, 0);
-
- if (g_strv_length (id_split) == 0) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Invalid id: '%s'",
- id);
- goto send_error;
- }
-
- category = atoi (id_split[0]);
-
- if (category == JAMENDO_ARTIST_CAT) {
- if (id_split[1]) {
- /* Requesting information from a specific artist */
- jamendo_keys = get_jamendo_keys (JAMENDO_ARTIST_CAT);
- url =
- g_strdup_printf (JAMENDO_GET_ARTIST,
- jamendo_keys,
- id_split[1]);
- g_free (jamendo_keys);
- } else {
- /* Requesting information from artist category */
- update_media_from_artists (ms->media);
- }
- } else if (category == JAMENDO_ALBUM_CAT) {
- if (id_split[1]) {
- /* Requesting information from a specific album */
- jamendo_keys = get_jamendo_keys (JAMENDO_ALBUM_CAT);
- url =
- g_strdup_printf (JAMENDO_GET_ALBUM,
- jamendo_keys,
- id_split[1]);
- g_free (jamendo_keys);
- } else {
- /* Requesting information from album category */
- update_media_from_albums (ms->media);
- }
- } else if (category == JAMENDO_TRACK_CAT) {
- if (id_split[1]) {
- /* Requesting information from a specific song */
- jamendo_keys = get_jamendo_keys (JAMENDO_TRACK_CAT);
- url =
- g_strdup_printf (JAMENDO_GET_TRACK,
- jamendo_keys,
- id_split[1]);
- g_free (jamendo_keys);
- } else {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Invalid id: '%s'",
- id);
- g_strfreev (id_split);
- goto send_error;
- }
- } else if (category == JAMENDO_FEEDS_CAT) {
- if (id_split[1]) {
- int i;
-
- i = atoi (id_split[0]);
- update_media_from_feed (ms->media, i);
- } else {
- update_media_from_feeds (ms->media);
- }
- } else {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Invalid id: '%s'",
- id);
- g_strfreev (id_split);
- goto send_error;
- }
- }
-
- if (id_split) {
- g_strfreev (id_split);
- }
-
- if (url) {
- xpe = g_slice_new0 (XmlParseEntries);
- xpe->type = METADATA;
- xpe->spec.ms = ms;
- read_url_async (GRL_JAMENDO_SOURCE (source), url, xpe);
- g_free (url);
- } else {
- if (ms->media) {
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- }
- }
-
- return;
-
- send_error:
- ms->callback (ms->source, NULL, ms->user_data, error);
- g_error_free (error);
-}
-
-static void
-grl_jamendo_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- gchar *url = NULL;
- gchar *jamendo_keys;
- gchar **container_split = NULL;
- JamendoCategory category;
- XmlParseEntries *xpe = NULL;
- const gchar *container_id;
- GError *error = NULL;
- guint page_size;
- guint page_number;
- guint page_offset;
-
- GRL_DEBUG ("grl_jamendo_source_browse");
-
- container_id = grl_media_get_id (bs->container);
-
- if (!container_id) {
- /* Root category: return top-level predefined categories */
- send_toplevel_categories (bs);
- return;
- }
-
- container_split = g_strsplit (container_id, JAMENDO_ID_SEP, 0);
-
- if (g_strv_length (container_split) == 0) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Invalid container-id: '%s'",
- container_id);
- } else {
- category = atoi (container_split[0]);
- grl_paging_translate (bs->skip,
- bs->count,
- 0,
- &page_size,
- &page_number,
- &page_offset);
-
- if (category == JAMENDO_ARTIST_CAT) {
- if (container_split[1]) {
- jamendo_keys = get_jamendo_keys (JAMENDO_ALBUM_CAT);
- /* Requesting information from a specific artist */
- url =
- g_strdup_printf (JAMENDO_GET_ALBUMS_FROM_ARTIST,
- jamendo_keys,
- page_size,
- page_number,
- container_split[1]);
- } else {
- /* Browsing through artists */
- jamendo_keys = get_jamendo_keys (JAMENDO_ARTIST_CAT);
- url = g_strdup_printf (JAMENDO_GET_ARTISTS,
- jamendo_keys,
- page_size,
- page_number);
- }
- g_free (jamendo_keys);
-
- } else if (category == JAMENDO_ALBUM_CAT) {
- if (container_split[1]) {
- /* Requesting information from a specific album */
- jamendo_keys = get_jamendo_keys (JAMENDO_TRACK_CAT);
- url =
- g_strdup_printf (JAMENDO_GET_TRACKS_FROM_ALBUM,
- jamendo_keys,
- page_size,
- page_number,
- container_split[1]);
- } else {
- /* Browsing through albums */
- jamendo_keys = get_jamendo_keys (JAMENDO_ALBUM_CAT);
- url = g_strdup_printf (JAMENDO_GET_ALBUMS,
- jamendo_keys,
- page_size,
- page_number);
- }
- g_free (jamendo_keys);
-
- } else if (category == JAMENDO_FEEDS_CAT) {
- if (container_split[1]) {
- int feed_id;
-
- feed_id = atoi (container_split[1]);
- jamendo_keys = get_jamendo_keys (feeds[feed_id].cat);
- url = g_strdup_printf (feeds[feed_id].url,
- jamendo_keys,
- page_size,
- page_number);
- } else {
- send_feeds (bs);
- return;
- }
-
- } else if (category == JAMENDO_TRACK_CAT) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Cannot browse through a track: '%s'",
- container_id);
- } else {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Invalid container-id: '%s'",
- container_id);
- }
- }
-
- if (error) {
- bs->callback (source, bs->browse_id, NULL, 0, bs->user_data, error);
- g_error_free (error);
- return;
- }
-
- xpe = g_slice_new0 (XmlParseEntries);
- xpe->type = BROWSE;
- xpe->offset = page_offset;
- xpe->spec.bs = bs;
-
- grl_media_source_set_operation_data (source, bs->browse_id, xpe);
-
- read_url_async (GRL_JAMENDO_SOURCE (source), url, xpe);
- g_free (url);
- if (container_split) {
- g_strfreev (container_split);
- }
-}
-
-/*
- * Query format is "<type>=<text>", where <type> can be either 'artist', 'album'
- * or 'track' and 'text' is the term to search.
- *
- * The result will be also a <type>.
- *
- * Example: search for artists that have the "Shake" in their name or
- * description: "artist=Shake"
- *
- */
-static void
-grl_jamendo_source_query (GrlMediaSource *source,
- GrlMediaSourceQuerySpec *qs)
-{
- GError *error = NULL;
- JamendoCategory category;
- gchar *term = NULL;
- gchar *url;
- gchar *jamendo_keys = NULL;
- gchar *query = NULL;
- XmlParseEntries *xpe = NULL;
- guint page_size;
- guint page_number;
- guint page_offset;
-
- GRL_DEBUG ("grl_jamendo_source_query");
-
- if (!parse_query (qs->query, &category, &term)) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_QUERY_FAILED,
- "Query malformed: '%s'",
- qs->query);
- goto send_error;
- }
-
- jamendo_keys = get_jamendo_keys (category);
- switch (category) {
- case JAMENDO_ARTIST_CAT:
- query = JAMENDO_SEARCH_ARTIST;
- break;
- case JAMENDO_ALBUM_CAT:
- query = JAMENDO_SEARCH_ALBUM;
- break;
- case JAMENDO_TRACK_CAT:
- query = JAMENDO_SEARCH_TRACK;
- break;
- default:
- g_return_if_reached ();
- }
-
- grl_paging_translate (qs->skip,
- qs->count,
- 0,
- &page_size,
- &page_number,
- &page_offset);
-
- url = g_strdup_printf (query,
- jamendo_keys,
- page_size,
- page_number,
- term);
- g_free (term);
-
- xpe = g_slice_new0 (XmlParseEntries);
- xpe->type = QUERY;
- xpe->offset = page_offset;
- xpe->spec.qs = qs;
-
- grl_media_source_set_operation_data (source, qs->query_id, xpe);
-
- read_url_async (GRL_JAMENDO_SOURCE (source), url, xpe);
- g_free (url);
-
- return;
-
- send_error:
- qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
- g_error_free (error);
-}
-
-
-static void
-grl_jamendo_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss)
-{
- XmlParseEntries *xpe;
- gchar *jamendo_keys;
- gchar *url;
- guint page_size;
- guint page_number;
- guint page_offset;
-
- GRL_DEBUG ("grl_jamendo_source_search");
-
- jamendo_keys = get_jamendo_keys (JAMENDO_TRACK_CAT);
-
- grl_paging_translate (ss->skip,
- ss->count,
- 0,
- &page_size,
- &page_number,
- &page_offset);
-
- if (ss->text) {
- url = g_strdup_printf (JAMENDO_SEARCH_TRACK,
- jamendo_keys,
- page_size,
- page_number,
- ss->text);
- } else {
- url = g_strdup_printf (JAMENDO_SEARCH_ALL,
- jamendo_keys,
- page_size,
- page_number);
- }
-
- xpe = g_slice_new0 (XmlParseEntries);
- xpe->type = SEARCH;
- xpe->offset = page_offset;
- xpe->spec.ss = ss;
-
- grl_media_source_set_operation_data (source, ss->search_id, xpe);
-
- read_url_async (GRL_JAMENDO_SOURCE (source), url, xpe);
- g_free (url);
-}
-
-static void
-grl_jamendo_source_cancel (GrlMediaSource *source, guint operation_id)
-{
- XmlParseEntries *xpe;
- GrlJamendoSourcePriv *priv;
-
- g_return_if_fail (GRL_IS_JAMENDO_SOURCE (source));
-
- priv = GRL_JAMENDO_SOURCE_GET_PRIVATE (source);
-
- if (priv->cancellable && G_IS_CANCELLABLE (priv->cancellable))
- g_cancellable_cancel (priv->cancellable);
- priv->cancellable = NULL;
-
- if (priv->wc)
- grl_net_wc_flush_delayed_requests (priv->wc);
-
- GRL_DEBUG ("grl_jamendo_source_cancel");
-
- xpe = (XmlParseEntries *) grl_media_source_get_operation_data (source,
- operation_id);
-
- if (xpe) {
- xpe->cancelled = TRUE;
- }
-}
diff --git a/src/jamendo/grl-jamendo.h b/src/jamendo/grl-jamendo.h
deleted file mode 100644
index 15a4b75..0000000
--- a/src/jamendo/grl-jamendo.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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_JAMENDO_SOURCE_H_
-#define _GRL_JAMENDO_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_JAMENDO_SOURCE_TYPE \
- (grl_jamendo_source_get_type ())
-
-#define GRL_JAMENDO_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_JAMENDO_SOURCE_TYPE, \
- GrlJamendoSource))
-
-#define GRL_IS_JAMENDO_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_JAMENDO_SOURCE_TYPE))
-
-#define GRL_JAMENDO_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_JAMENDO_SOURCE_TYPE, \
- GrlJamendoSourceClass))
-
-#define GRL_IS_JAMENDO_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_JAMENDO_SOURCE_TYPE))
-
-#define GRL_JAMENDO_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_JAMENDO_SOURCE_TYPE, \
- GrlJamendoSourceClass))
-
-typedef struct _GrlJamendoSource GrlJamendoSource;
-typedef struct _GrlJamendoSourcePriv GrlJamendoSourcePriv;
-
-struct _GrlJamendoSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlJamendoSourcePriv *priv;
-
-};
-
-typedef struct _GrlJamendoSourceClass GrlJamendoSourceClass;
-
-struct _GrlJamendoSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_jamendo_source_get_type (void);
-
-#endif /* _GRL_JAMENDO_SOURCE_H_ */
diff --git a/src/jamendo/grl-jamendo.xml b/src/jamendo/grl-jamendo.xml
deleted file mode 100644
index e864cfe..0000000
--- a/src/jamendo/grl-jamendo.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Jamendo</name>
- <description>A plugin for browsing and searching Jamendo videos</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/lastfm-albumart/Makefile.am b/src/lastfm-albumart/Makefile.am
deleted file mode 100644
index ad7bc6e..0000000
--- a/src/lastfm-albumart/Makefile.am
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Juan A. Suarez Romero <jasuarez igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrllastfm-albumart.la
-
-libgrllastfm_albumart_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GRLNET_CFLAGS) \
- $(XML_CFLAGS)
-
-libgrllastfm_albumart_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GRLNET_LIBS) \
- $(XML_LIBS)
-
-libgrllastfm_albumart_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrllastfm_albumart_la_SOURCES = grl-lastfm-albumart.c grl-lastfm-albumart.h
-
-libdir = $(GRL_PLUGINS_DIR)
-lastfmalbumartxmldir = $(GRL_PLUGINS_CONF_DIR)
-lastfmalbumartxml_DATA = $(LASTFM_ALBUMART_PLUGIN_ID).xml
-
-EXTRA_DIST = $(lastfmalbumartxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/lastfm-albumart/grl-lastfm-albumart.c b/src/lastfm-albumart/grl-lastfm-albumart.c
deleted file mode 100644
index 99825a2..0000000
--- a/src/lastfm-albumart/grl-lastfm-albumart.c
+++ /dev/null
@@ -1,316 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <net/grl-net.h>
-#include <libxml/parser.h>
-#include <libxml/xmlmemory.h>
-#include <libxml/xpath.h>
-
-#include "grl-lastfm-albumart.h"
-
-/* ---------- Logging ---------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT lastfm_albumart_log_domain
-GRL_LOG_DOMAIN_STATIC(lastfm_albumart_log_domain);
-
-/* -------- Last.FM API -------- */
-
-#define LASTFM_GET_ALBUM "http://ws.audioscrobbler.com/1.0/album/%s/%s/info.xml"
-#define LASTFM_XML_COVER "/album/coverart/medium"
-
-/* ------- Pluging Info -------- */
-
-#define PLUGIN_ID LASTFM_ALBUMART_PLUGIN_ID
-
-#define SOURCE_ID "grl-lastfm-albumart"
-#define SOURCE_NAME "Album art Provider from Last.FM"
-#define SOURCE_DESC "A plugin for getting album arts using Last.FM as backend"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-static GrlNetWc *wc;
-
-static GrlLastfmAlbumartSource *grl_lastfm_albumart_source_new (void);
-
-static void grl_lastfm_albumart_source_finalize (GObject *object);
-
-static void grl_lastfm_albumart_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs);
-
-static const GList *grl_lastfm_albumart_source_supported_keys (GrlMetadataSource *source);
-
-static const GList *grl_lastfm_albumart_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id);
-
-gboolean grl_lastfm_albumart_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-
-/* =================== Last.FM-AlbumArt Plugin =============== */
-
-gboolean
-grl_lastfm_albumart_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (lastfm_albumart_log_domain, "lastfm-albumart");
-
- GRL_DEBUG ("grl_lastfm_albumart_source_plugin_init");
-
- GrlLastfmAlbumartSource *source = grl_lastfm_albumart_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_lastfm_albumart_source_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Last.FM-AlbumArt GObject ================ */
-
-static GrlLastfmAlbumartSource *
-grl_lastfm_albumart_source_new (void)
-{
- GRL_DEBUG ("grl_lastfm_albumart_source_new");
- return g_object_new (GRL_LASTFM_ALBUMART_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_lastfm_albumart_source_class_init (GrlLastfmAlbumartSourceClass * klass)
-{
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- metadata_class->supported_keys = grl_lastfm_albumart_source_supported_keys;
- metadata_class->key_depends = grl_lastfm_albumart_source_key_depends;
- metadata_class->resolve = grl_lastfm_albumart_source_resolve;
-
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- gobject_class->finalize = grl_lastfm_albumart_source_finalize;
-}
-
-static void
-grl_lastfm_albumart_source_init (GrlLastfmAlbumartSource *source)
-{
-}
-
-G_DEFINE_TYPE (GrlLastfmAlbumartSource,
- grl_lastfm_albumart_source,
- GRL_TYPE_METADATA_SOURCE);
-
-static void
-grl_lastfm_albumart_source_finalize (GObject *object)
-{
- if (wc && GRL_IS_NET_WC (wc))
- g_object_unref (wc);
-
- G_OBJECT_CLASS (grl_lastfm_albumart_source_parent_class)->finalize (object);
-}
-
-/* ======================= Utilities ==================== */
-
-static gchar *
-xml_get_image (const gchar *xmldata)
-{
- xmlDocPtr doc;
- xmlXPathContextPtr xpath_ctx;
- xmlXPathObjectPtr xpath_res;
- gchar *image = NULL;
-
- doc = xmlReadMemory (xmldata, xmlStrlen ((xmlChar*) xmldata), NULL, NULL,
- XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- if (!doc) {
- return NULL;
- }
-
- xpath_ctx = xmlXPathNewContext (doc);
- if (!xpath_ctx) {
- xmlFreeDoc (doc);
- return NULL;
- }
-
- xpath_res = xmlXPathEvalExpression ((xmlChar *) LASTFM_XML_COVER,
- xpath_ctx);
- if (!xpath_res) {
- xmlXPathFreeContext (xpath_ctx);
- xmlFreeDoc (doc);
- return NULL;
- }
-
- if (xpath_res->nodesetval->nodeTab) {
- image =
- (gchar *) xmlNodeListGetString (doc,
- xpath_res->nodesetval->nodeTab[0]->xmlChildrenNode,
- 1);
- }
- xmlXPathFreeObject (xpath_res);
- xmlXPathFreeContext (xpath_ctx);
- xmlFreeDoc (doc);
-
- return image;
-}
-
-static void
-read_done_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- GrlMetadataSourceResolveSpec *rs =
- (GrlMetadataSourceResolveSpec *) user_data;
- GError *error = NULL;
- GError *wc_error = NULL;
- gchar *content = NULL;
- gchar *image = NULL;
-
- if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
- res,
- &content,
- NULL,
- &wc_error)) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_RESOLVE_FAILED,
- "Failed to connect to Last.FM: '%s'",
- wc_error->message);
- rs->callback (rs->source, rs->media, rs->user_data, error);
- g_error_free (wc_error);
- g_error_free (error);
-
- return;
- }
-
- image = xml_get_image (content);
- if (image) {
- grl_data_set_string (GRL_DATA (rs->media),
- GRL_METADATA_KEY_THUMBNAIL,
- image);
- g_free (image);
- }
-
- rs->callback (rs->source, rs->media, rs->user_data, NULL);
-}
-
-static void
-read_url_async (const gchar *url, gpointer user_data)
-{
- if (!wc)
- wc = grl_net_wc_new ();
-
- GRL_DEBUG ("Opening '%s'", url);
- grl_net_wc_request_async (wc, url, NULL, read_done_cb, user_data);
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_lastfm_albumart_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
-
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_THUMBNAIL,
- NULL);
- }
-
- return keys;
-}
-
-static const GList *
-grl_lastfm_albumart_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id)
-{
- static GList *deps = NULL;
-
- if (!deps) {
- deps = grl_metadata_key_list_new (GRL_METADATA_KEY_ARTIST,
- GRL_METADATA_KEY_ALBUM,
- NULL);
- }
-
- if (key_id == GRL_METADATA_KEY_THUMBNAIL) {
- return deps;
- }
-
- return NULL;
-}
-
-static void
-grl_lastfm_albumart_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs)
-{
- const gchar *artist = NULL;
- const gchar *album = NULL;
- gchar *esc_artist = NULL;
- gchar *esc_album = NULL;
- gchar *url = NULL;
-
- GRL_DEBUG ("grl_lastfm_albumart_source_resolve");
-
- GList *iter;
-
- /* Check that albumart is requested */
- iter = rs->keys;
- while (iter) {
- if (iter->data == GRL_METADATA_KEY_THUMBNAIL) {
- break;
- } else {
- iter = g_list_next (iter);
- }
- }
-
- if (iter == NULL) {
- GRL_DEBUG ("No supported key was requested");
- rs->callback (source, rs->media, rs->user_data, NULL);
- } else {
- artist = grl_data_get_string (GRL_DATA (rs->media),
- GRL_METADATA_KEY_ARTIST);
-
- album = grl_data_get_string (GRL_DATA (rs->media),
- GRL_METADATA_KEY_ALBUM);
-
- if (!artist || !album) {
- GRL_DEBUG ("Missing dependencies");
- rs->callback (source, rs->media, rs->user_data, NULL);
- } else {
- esc_artist = g_uri_escape_string (artist, NULL, TRUE);
- esc_album = g_uri_escape_string (album, NULL, TRUE);
- url = g_strdup_printf (LASTFM_GET_ALBUM, esc_artist, esc_album);
- read_url_async (url, rs);
- g_free (esc_artist);
- g_free (esc_album);
- g_free (url);
- }
- }
-}
diff --git a/src/lastfm-albumart/grl-lastfm-albumart.h b/src/lastfm-albumart/grl-lastfm-albumart.h
deleted file mode 100644
index 43a5ab5..0000000
--- a/src/lastfm-albumart/grl-lastfm-albumart.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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_LASTFM_ALBUMART_SOURCE_H_
-#define _GRL_LASTFM_ALBUMART_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_LASTFM_ALBUMART_SOURCE_TYPE \
- (grl_lastfm_albumart_source_get_type ())
-
-#define GRL_LASTFM_ALBUMART_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_LASTFM_ALBUMART_SOURCE_TYPE, \
- GrlLastfmAlbumartSource))
-
-#define GRL_IS_LASTFM_ALBUMART_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_LASTFM_ALBUMART_SOURCE_TYPE))
-
-#define GRL_LASTFM_ALBUMART_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_LASTFM_ALBUMART_SOURCE_TYPE, \
- GrlLastfmAlbumartSourceClass))
-
-#define GRL_IS_LASTFM_ALBUMART_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_LASTFM_ALBUMART_SOURCE_TYPE))
-
-#define GRL_LASTFM_ALBUMART_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_LASTFM_ALBUMART_SOURCE_TYPE, \
- GrlLastfmAlbumartSourceClass))
-
-typedef struct _GrlLastfmAlbumartSource GrlLastfmAlbumartSource;
-
-struct _GrlLastfmAlbumartSource {
-
- GrlMetadataSource parent;
-
-};
-
-typedef struct _GrlLastfmAlbumartSourceClass GrlLastfmAlbumartSourceClass;
-
-struct _GrlLastfmAlbumartSourceClass {
-
- GrlMetadataSourceClass parent_class;
-
-};
-
-GType grl_lastfm_albumart_source_get_type (void);
-
-#endif /* _GRL_LASTFM_ALBUMART_SOURCE_H_ */
diff --git a/src/lastfm-albumart/grl-lastfm-albumart.xml b/src/lastfm-albumart/grl-lastfm-albumart.xml
deleted file mode 100644
index f2b64dd..0000000
--- a/src/lastfm-albumart/grl-lastfm-albumart.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Album art Provider from Last.FM</name>
- <description>A plugin for getting album arts using Last.FM as backend</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/local-metadata/Makefile.am b/src/local-metadata/Makefile.am
deleted file mode 100644
index 6840cf3..0000000
--- a/src/local-metadata/Makefile.am
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Guillaume Emont <gemont igalia com>
-#
-# Copyright (C) 2010-2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrllocalmetadata.la
-
-libgrllocalmetadata_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GIO_CFLAGS)
-
-libgrllocalmetadata_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GIO_LIBS)
-
-libgrllocalmetadata_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrllocalmetadata_la_SOURCES = grl-local-metadata.c grl-local-metadata.h
-
-libdir=$(GRL_PLUGINS_DIR)
-localmetadataxmldir = $(GRL_PLUGINS_CONF_DIR)
-localmetadataxml_DATA = $(LOCALMETADATA_PLUGIN_ID).xml
-
-EXTRA_DIST = $(localmetadataxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/local-metadata/grl-local-metadata.c b/src/local-metadata/grl-local-metadata.c
deleted file mode 100644
index a535f42..0000000
--- a/src/local-metadata/grl-local-metadata.c
+++ /dev/null
@@ -1,262 +0,0 @@
-/*
- * Copyright (C) 2010-2011 Igalia S.L.
- *
- * Contact: Guillaume Emont <gemont igalia 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 <grilo.h>
-#include <gio/gio.h>
-
-#include "grl-local-metadata.h"
-
-#define GRL_LOG_DOMAIN_DEFAULT local_metadata_log_domain
-GRL_LOG_DOMAIN_STATIC(local_metadata_log_domain);
-
-#define PLUGIN_ID LOCALMETADATA_PLUGIN_ID
-
-#define SOURCE_ID "grl-local-metadata"
-#define SOURCE_NAME "Local Metadata Provider"
-#define SOURCE_DESC "A source providing locally available metadata"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-
-static GrlLocalMetadataSource *grl_local_metadata_source_new (void);
-
-static void grl_local_metadata_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs);
-
-static const GList *grl_local_metadata_source_supported_keys (GrlMetadataSource *source);
-
-static const GList * grl_local_metadata_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id);
-
-gboolean grl_local_metadata_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-
-/* =================== GrlLocalMetadata Plugin =============== */
-
-gboolean
-grl_local_metadata_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (local_metadata_log_domain, "local-metadata");
-
- GRL_DEBUG ("grl_local_metadata_source_plugin_init");
-
- GrlLocalMetadataSource *source = grl_local_metadata_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_local_metadata_source_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== GrlLocalMetadata GObject ================ */
-
-static GrlLocalMetadataSource *
-grl_local_metadata_source_new (void)
-{
- GRL_DEBUG ("grl_local_metadata_source_new");
- return g_object_new (GRL_LOCAL_METADATA_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_local_metadata_source_class_init (GrlLocalMetadataSourceClass * klass)
-{
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- metadata_class->supported_keys = grl_local_metadata_source_supported_keys;
- metadata_class->key_depends = grl_local_metadata_source_key_depends;
- metadata_class->resolve = grl_local_metadata_source_resolve;
-}
-
-static void
-grl_local_metadata_source_init (GrlLocalMetadataSource *source)
-{
-}
-
-G_DEFINE_TYPE (GrlLocalMetadataSource,
- grl_local_metadata_source,
- GRL_TYPE_METADATA_SOURCE);
-
-/* ======================= Utilities ==================== */
-static void
-got_file_info (GFile *file, GAsyncResult *result,
- GrlMetadataSourceResolveSpec *rs)
-{
- GFileInfo *info;
- GError *error = NULL;
- const gchar *thumbnail_path;
-
- GRL_DEBUG ("got_file_info");
-
- info = g_file_query_info_finish (file, result, &error);
- if (error)
- goto error;
-
- thumbnail_path =
- g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
-
-
- if (thumbnail_path) {
- gchar *thumbnail_uri = g_filename_to_uri (thumbnail_path, NULL, &error);
- if (error)
- goto error;
-
- GRL_INFO ("Got thumbnail %s for media: %s", thumbnail_uri,
- grl_media_get_url (rs->media));
- grl_media_set_thumbnail (rs->media, thumbnail_uri);
- g_free (thumbnail_uri);
-
- rs->callback (rs->source, rs->media, rs->user_data, NULL);
- } else {
- GRL_INFO ("Could not find thumbnail for media: %s",
- grl_media_get_url (rs->media));
- rs->callback (rs->source, rs->media, rs->user_data, NULL);
- }
-
- goto exit;
-
-error:
- {
- GError *new_error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
- "Got error: %s", error->message);
- rs->callback (rs->source, rs->media, rs->user_data, new_error);
-
- g_error_free (error);
- g_error_free (new_error);
- }
-
-exit:
- if (info)
- g_object_unref (info);
-}
-
-static void
-resolve_image (GrlMetadataSourceResolveSpec *rs)
-{
- GFile *file;
-
- GRL_DEBUG ("resolve_image");
-
- file = g_file_new_for_uri (grl_media_get_url (rs->media));
-
- g_file_query_info_async (file, G_FILE_ATTRIBUTE_THUMBNAIL_PATH,
- G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, NULL,
- (GAsyncReadyCallback)got_file_info, rs);
-}
-
-static void
-resolve_album_art (GrlMetadataSourceResolveSpec *rs)
-{
- /* FIXME: implement this, according to
- * http://live.gnome.org/MediaArtStorageSpec */
- GError *error;
- error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
- "Thumbnail resolution for GrlMediaAudio not implemented in local-metadata");
- rs->callback (rs->source, rs->media, rs->user_data, error);
- g_error_free (error);
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_local_metadata_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_THUMBNAIL,
- NULL);
- }
- return keys;
-}
-
-static const GList *
-grl_local_metadata_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id)
-{
- static GList *deps = NULL;
- if (!deps) {
- deps = grl_metadata_key_list_new (GRL_METADATA_KEY_URL, NULL);
- }
-
- if (key_id == GRL_METADATA_KEY_THUMBNAIL)
- return deps;
-
- return NULL;
-}
-
-static void
-grl_local_metadata_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs)
-{
- const gchar *url;
- gchar *scheme;
- GError *error = NULL;
-
- GRL_DEBUG ("grl_local_metadata_source_resolve");
-
- url = grl_media_get_url (rs->media);
- scheme = g_uri_parse_scheme (url);
-
- if (0 != g_strcmp0 (scheme, "file"))
- error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
- "local-metadata needs a url in the file:// scheme");
- else if (!g_list_find (rs->keys, GRL_METADATA_KEY_THUMBNAIL))
- error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
- "local-metadata can only resolve the thumbnail key");
-
- if (error) {
- /* No can do! */
- rs->callback (source, rs->media, rs->user_data, error);
- g_error_free (error);
- goto exit;
- }
-
- if (GRL_IS_MEDIA_VIDEO (rs->media)
- || GRL_IS_MEDIA_IMAGE (rs->media)) {
- resolve_image (rs);
- } else if (GRL_IS_MEDIA_AUDIO (rs->media)) {
- resolve_album_art (rs);
- } else {
- /* What's that media type? */
- rs->callback (source, rs->media, rs->user_data, NULL);
- }
-
-exit:
- g_free (scheme);
-}
-
diff --git a/src/local-metadata/grl-local-metadata.h b/src/local-metadata/grl-local-metadata.h
deleted file mode 100644
index 448051e..0000000
--- a/src/local-metadata/grl-local-metadata.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * Copyright (C) 2010-2011 Igalia S.L.
- *
- * Contact: Guillaume Emont <gemont igalia 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_LOCAL_METADATA_SOURCE_H_
-#define _GRL_LOCAL_METADATA_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_LOCAL_METADATA_SOURCE_TYPE \
- (grl_local_metadata_source_get_type ())
-
-#define GRL_LOCAL_METADATA_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_LOCAL_METADATA_SOURCE_TYPE, \
- GrlLocalMetadataSource))
-
-#define GRL_IS_LOCAL_METADATA_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_LOCAL_METADATA_SOURCE_TYPE))
-
-#define GRL_LOCAL_METADATA_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_LOCAL_METADATA_SOURCE_TYPE, \
- GrlLocalMetadataSourceClass))
-
-#define GRL_IS_LOCAL_METADATA_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_LOCAL_METADATA_SOURCE_TYPE))
-
-#define GRL_LOCAL_METADATA_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_LOCAL_METADATA_SOURCE_TYPE, \
- GrlLocalMetadataSourceClass))
-
-typedef struct _GrlLocalMetadataSource GrlLocalMetadataSource;
-
-struct _GrlLocalMetadataSource {
-
- GrlMetadataSource parent;
-
-};
-
-typedef struct _GrlLocalMetadataSourceClass GrlLocalMetadataSourceClass;
-
-struct _GrlLocalMetadataSourceClass {
-
- GrlMetadataSourceClass parent_class;
-
-};
-
-GType grl_local_metadata_source_get_type (void);
-
-#endif /* _GRL_LOCAL_METADATA_SOURCE_H_ */
diff --git a/src/local-metadata/grl-local-metadata.xml b/src/local-metadata/grl-local-metadata.xml
deleted file mode 100644
index 073392e..0000000
--- a/src/local-metadata/grl-local-metadata.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Local Metadata Provider</name>
- <description>A plugin that gets simple-to-obtain metadata from the local system</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/media/apple-trailers/Makefile.am b/src/media/apple-trailers/Makefile.am
new file mode 100644
index 0000000..0195de5
--- /dev/null
+++ b/src/media/apple-trailers/Makefile.am
@@ -0,0 +1,38 @@
+#
+# Makefile.am
+#
+# Author: Juan A. Suarez Romero <jasuarez igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlappletrailers.la
+
+libgrlappletrailers_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GRLNET_CFLAGS) \
+ $(XML_CFLAGS)
+
+libgrlappletrailers_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GRLNET_LIBS) \
+ $(XML_LIBS)
+
+libgrlappletrailers_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlappletrailers_la_SOURCES = \
+ grl-apple-trailers.c \
+ grl-apple-trailers.h
+
+libdir = $(GRL_PLUGINS_DIR)
+appletrailersxmldir = $(GRL_PLUGINS_CONF_DIR)
+appletrailersxml_DATA = $(APPLE_TRAILERS_PLUGIN_ID).xml
+
+EXTRA_DIST = $(appletrailersxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/apple-trailers/grl-apple-trailers.c b/src/media/apple-trailers/grl-apple-trailers.c
new file mode 100644
index 0000000..b81748b
--- /dev/null
+++ b/src/media/apple-trailers/grl-apple-trailers.c
@@ -0,0 +1,611 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
+#include <net/grl-net.h>
+#include <libxml/xpath.h>
+
+#include "grl-apple-trailers.h"
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT apple_trailers_log_domain
+GRL_LOG_DOMAIN_STATIC(apple_trailers_log_domain);
+
+/* ---- Apple Trailers Service ---- */
+
+#define APPLE_TRAILERS_CURRENT_SD \
+ "http://trailers.apple.com/trailers/home/xml/current_480p.xml"
+
+#define APPLE_TRAILERS_CURRENT_HD \
+ "http://trailers.apple.com/trailers/home/xml/current_720p.xml"
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID APPLE_TRAILERS_PLUGIN_ID
+
+#define SOURCE_ID "grl-apple-trailers"
+#define SOURCE_NAME "Apple Movie Trailers"
+#define SOURCE_DESC "A plugin for browsing Apple Movie Trailers"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+typedef struct {
+ GrlMediaSourceBrowseSpec *bs;
+ xmlDocPtr xml_doc;
+ xmlNodePtr xml_entries;
+ gboolean cancelled;
+} OperationData;
+
+enum {
+ PROP_0,
+ PROP_HD,
+ PROP_LARGE_POSTER,
+};
+
+struct _GrlAppleTrailersSourcePriv {
+ GrlNetWc *wc;
+ GCancellable *cancellable;
+ gboolean hd;
+ gboolean large_poster;
+};
+
+#define GRL_APPLE_TRAILERS_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_APPLE_TRAILERS_SOURCE_TYPE, \
+ GrlAppleTrailersSourcePriv))
+
+static GrlAppleTrailersSource *grl_apple_trailers_source_new (gboolean hd,
+ gboolean xlarge);
+
+gboolean grl_apple_trailers_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+static const GList *grl_apple_trailers_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_apple_trailers_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+
+static void grl_apple_trailers_source_cancel (GrlMediaSource *source,
+ guint operation_id);
+
+/* =================== Apple Trailers Plugin =============== */
+
+gboolean
+grl_apple_trailers_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GrlAppleTrailersSource *source;
+ gboolean hd = FALSE;
+ gboolean xlarge = FALSE;
+
+ GRL_LOG_DOMAIN_INIT (apple_trailers_log_domain, "apple-trailers");
+
+ GRL_DEBUG ("apple_trailers_plugin_init");
+
+ for (; configs; configs = g_list_next (configs)) {
+ GrlConfig *config;
+ gchar *definition, *poster_size;
+
+ config = GRL_CONFIG (configs->data);
+ definition = grl_config_get_string (config, "definition");
+ if (definition) {
+ if (*definition != '\0') {
+ if (g_str_equal (definition, "hd")) {
+ hd = TRUE;
+ }
+ }
+ g_free (definition);
+ }
+
+ poster_size = grl_config_get_string (config, "poster-size");
+ if (poster_size) {
+ if (*poster_size != '\0') {
+ if (g_str_equal (poster_size, "xlarge")) {
+ xlarge = TRUE;
+ }
+ }
+ g_free (poster_size);
+ }
+ }
+
+ source = grl_apple_trailers_source_new (hd, xlarge);
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_apple_trailers_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== AppleTrailers GObject ================ */
+
+static GrlAppleTrailersSource *
+grl_apple_trailers_source_new (gboolean high_definition,
+ gboolean xlarge)
+{
+ GrlAppleTrailersSource *source;
+
+ GRL_DEBUG ("grl_apple_trailers_source_new%s%s",
+ high_definition ? " (HD)" : "",
+ xlarge ? " (X-large poster)" : "");
+ source = g_object_new (GRL_APPLE_TRAILERS_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ "high-definition", high_definition,
+ "large-poster", xlarge,
+ NULL);
+
+ return source;
+}
+
+G_DEFINE_TYPE (GrlAppleTrailersSource, grl_apple_trailers_source, GRL_TYPE_MEDIA_SOURCE);
+
+static void
+grl_apple_trailers_source_finalize (GObject *object)
+{
+ GrlAppleTrailersSource *self;
+
+ self = GRL_APPLE_TRAILERS_SOURCE (object);
+
+ if (self->priv->wc)
+ g_object_unref (self->priv->wc);
+
+ if (self->priv->cancellable
+ && G_IS_CANCELLABLE (self->priv->cancellable))
+ g_object_unref (self->priv->cancellable);
+
+ G_OBJECT_CLASS (grl_apple_trailers_source_parent_class)->finalize (object);
+}
+
+static void
+grl_apple_trailers_source_set_property (GObject *object,
+ guint propid,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GrlAppleTrailersSource *self;
+ self = GRL_APPLE_TRAILERS_SOURCE (object);
+
+ switch (propid) {
+ case PROP_HD:
+ self->priv->hd = g_value_get_boolean (value);
+ break;
+ case PROP_LARGE_POSTER:
+ self->priv->large_poster = g_value_get_boolean (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+}
+
+static void
+grl_apple_trailers_source_class_init (GrlAppleTrailersSourceClass * klass)
+{
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ GObjectClass *g_class = G_OBJECT_CLASS (klass);
+ source_class->browse = grl_apple_trailers_source_browse;
+ source_class->cancel = grl_apple_trailers_source_cancel;
+ metadata_class->supported_keys = grl_apple_trailers_source_supported_keys;
+ g_class->finalize = grl_apple_trailers_source_finalize;
+ g_class->set_property = grl_apple_trailers_source_set_property;
+
+ g_object_class_install_property (g_class,
+ PROP_HD,
+ g_param_spec_boolean ("high-definition",
+ "hd",
+ "Hi/Low definition videos",
+ TRUE,
+ G_PARAM_WRITABLE
+ | G_PARAM_CONSTRUCT_ONLY
+ | G_PARAM_STATIC_NAME));
+
+ g_object_class_install_property (g_class,
+ PROP_LARGE_POSTER,
+ g_param_spec_boolean ("large-poster",
+ "xlarge",
+ "Pick large poster",
+ TRUE,
+ G_PARAM_WRITABLE
+ | G_PARAM_CONSTRUCT_ONLY
+ | G_PARAM_STATIC_NAME));
+
+ g_type_class_add_private (klass, sizeof (GrlAppleTrailersSourcePriv));
+}
+
+static void
+grl_apple_trailers_source_init (GrlAppleTrailersSource *source)
+{
+ source->priv = GRL_APPLE_TRAILERS_SOURCE_GET_PRIVATE (source);
+ source->priv->hd = TRUE;
+}
+
+/* ==================== Private ==================== */
+
+static gchar *
+get_node_value (xmlNodePtr node, const gchar *node_id)
+{
+ gchar *value;
+ xmlXPathContextPtr xpath_ctx;
+ xmlXPathObjectPtr xpath_res;
+
+ xpath_ctx = xmlXPathNewContext (node->doc);
+ if (!xpath_ctx) {
+ return NULL;
+ }
+
+ xpath_res = xmlXPathEvalExpression ((xmlChar *) node_id, xpath_ctx);
+ if (!xpath_res) {
+ xmlXPathFreeContext (xpath_ctx);
+ return NULL;
+ }
+
+ if (xpath_res->nodesetval->nodeTab) {
+ value =
+ (gchar *) xmlNodeListGetString (node->doc,
+ xpath_res->nodesetval->nodeTab[0]->xmlChildrenNode,
+ 1);
+ } else {
+ value = NULL;
+ }
+
+ xmlXPathFreeObject (xpath_res);
+ xmlXPathFreeContext (xpath_ctx);
+
+ return value;
+}
+
+static gint
+runtime_to_seconds (const gchar *runtime)
+{
+ gchar **items;
+ gint seconds;
+
+ if (!runtime) {
+ return 0;
+ }
+
+ seconds = 0;
+ items = g_strsplit (runtime, ":", -1);
+ if (items && items[0] && items[1])
+ seconds = 3600 * atoi (items[0]) + 60 * atoi (items[1]);
+ g_strfreev (items);
+
+ return seconds;
+}
+static GrlMedia *
+build_media_from_movie (xmlNodePtr node, gboolean xlarge)
+{
+ GrlMedia * media;
+ gchar *movie_author;
+ gchar *movie_date;
+ gchar *movie_description;
+ gchar *movie_duration;
+ gchar *movie_genre;
+ gchar *movie_id;
+ gchar *movie_thumbnail;
+ gchar *movie_title;
+ gchar *movie_url;
+ gchar *movie_rating;
+ gchar *movie_studio;
+ gchar *movie_copyright;
+
+ media = grl_media_video_new ();
+
+ movie_id = (gchar *) xmlGetProp (node, (const xmlChar *) "id");
+
+ /* HACK: as get_node_value applies xpath expression from root node, but we
+ want to do from current node, dup the node and mark it as root node */
+
+ xmlDocPtr xml_doc = xmlNewDoc ((const xmlChar *) "1.0");
+ xmlNodePtr node_dup = xmlCopyNode (node, 1);
+ xmlDocSetRootElement (xml_doc, node_dup);
+ movie_author = get_node_value (node_dup, "/movieinfo/info/director");
+ movie_date = get_node_value (node_dup, "/movieinfo/info/releasedate");
+ movie_description = get_node_value (node_dup, "/movieinfo/info/description");
+ movie_duration = get_node_value (node_dup, "/movieinfo/info/runtime");
+ movie_title = get_node_value (node_dup, "/movieinfo/info/title");
+ movie_genre = get_node_value (node_dup, "/movieinfo/genre/name");
+ if (xlarge)
+ movie_thumbnail = get_node_value (node_dup, "/movieinfo/poster/xlarge");
+ else
+ movie_thumbnail = get_node_value (node_dup, "/movieinfo/poster/location");
+ movie_url = get_node_value (node_dup, "/movieinfo/preview/large");
+ movie_rating = get_node_value (node_dup, "/movieinfo/info/rating");
+ movie_studio = get_node_value (node_dup, "/movieinfo/info/studio");
+ movie_copyright = get_node_value (node_dup, "/movieinfo/info/copyright");
+ xmlFreeDoc (xml_doc);
+
+ grl_media_set_id (media, movie_id);
+ grl_media_set_author (media, movie_author);
+ grl_media_set_date (media, movie_date);
+ grl_media_set_description (media, movie_description);
+ grl_media_set_duration (media, runtime_to_seconds (movie_duration));
+ grl_media_set_title (media, movie_title);
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_GENRE,
+ movie_genre);
+ grl_media_set_thumbnail (media, movie_thumbnail);
+ grl_media_set_url (media, movie_url);
+ grl_media_set_certificate (media, movie_rating);
+ grl_media_set_studio (media, movie_studio);
+
+ /* FIXME: Translation */
+ grl_media_set_license (media, movie_copyright);
+
+ g_free (movie_id);
+ g_free (movie_author);
+ g_free (movie_date);
+ g_free (movie_description);
+ g_free (movie_duration);
+ g_free (movie_title);
+ g_free (movie_genre);
+ g_free (movie_thumbnail);
+ g_free (movie_url);
+ g_free (movie_rating);
+ g_free (movie_studio);
+ g_free (movie_copyright);
+
+ return media;
+}
+
+static gboolean
+send_movie_info (OperationData *op_data)
+{
+ GrlMedia *media;
+ gboolean last = FALSE;
+
+ if (op_data->cancelled) {
+ op_data->bs->callback (op_data->bs->source,
+ op_data->bs->browse_id,
+ NULL,
+ 0,
+ op_data->bs->user_data,
+ NULL);
+ last = TRUE;
+ } else {
+ GrlAppleTrailersSource *source =
+ GRL_APPLE_TRAILERS_SOURCE (op_data->bs->source);
+
+ media = build_media_from_movie (op_data->xml_entries,
+ source->priv->large_poster);
+ last =
+ !op_data->xml_entries->next ||
+ op_data->bs->count == 1;
+
+ op_data->bs->callback (op_data->bs->source,
+ op_data->bs->browse_id,
+ media,
+ last? 0: -1,
+ op_data->bs->user_data,
+ NULL);
+ op_data->xml_entries = op_data->xml_entries->next;
+ if (!last)
+ op_data->bs->count--;
+ }
+
+ if (last) {
+ xmlFreeDoc (op_data->xml_doc);
+ g_slice_free (OperationData, op_data);
+ }
+
+ return !last;
+}
+
+static void
+xml_parse_result (const gchar *str, OperationData *op_data)
+{
+ GError *error = NULL;
+ xmlNodePtr node;
+
+ if (op_data->cancelled || op_data->bs->count == 0) {
+ goto finalize;
+ }
+
+ op_data->xml_doc = xmlReadMemory (str, xmlStrlen ((xmlChar*) str), NULL, NULL,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ if (!op_data->xml_doc) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Failed to parse response");
+ goto finalize;
+ }
+
+ node = xmlDocGetRootElement (op_data->xml_doc);
+ if (!node || !node->xmlChildrenNode) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Empty response from Apple Trailers");
+ goto finalize;
+ }
+
+ node = node->xmlChildrenNode;
+
+ /* Skip elements */
+ while (node && op_data->bs->skip > 0) {
+ node = node->next;
+ op_data->bs->skip--;
+ }
+
+ if (!node) {
+ goto finalize;
+ } else {
+ op_data->xml_entries = node;
+ g_idle_add ((GSourceFunc) send_movie_info, op_data);
+ }
+
+ return;
+
+ finalize:
+ op_data->bs->callback (op_data->bs->source,
+ op_data->bs->browse_id,
+ NULL,
+ 0,
+ op_data->bs->user_data,
+ error);
+
+ if (op_data->xml_doc) {
+ xmlFreeDoc (op_data->xml_doc);
+ }
+
+ if (error) {
+ g_error_free (error);
+ }
+
+ g_slice_free (OperationData, op_data);
+}
+
+static void
+read_done_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GError *wc_error = NULL;
+ OperationData *op_data = (OperationData *) user_data;
+ gchar *content = NULL;
+
+ if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res,
+ &content,
+ NULL,
+ &wc_error)) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Failed to connect Apple Trailers: '%s'",
+ wc_error->message);
+ op_data->bs->callback (op_data->bs->source,
+ op_data->bs->browse_id,
+ NULL,
+ 0,
+ op_data->bs->user_data,
+ error);
+ g_error_free (wc_error);
+ g_error_free (error);
+ g_slice_free (OperationData, op_data);
+
+ return;
+ }
+
+ xml_parse_result (content, op_data);
+}
+
+static void
+read_url_async (GrlAppleTrailersSource *source,
+ const gchar *url,
+ gpointer user_data)
+{
+ if (!source->priv->wc)
+ source->priv->wc = grl_net_wc_new ();
+
+ source->priv->cancellable = g_cancellable_new ();
+
+ GRL_DEBUG ("Opening '%s'", url);
+ grl_net_wc_request_async (source->priv->wc,
+ url,
+ source->priv->cancellable,
+ read_done_cb,
+ user_data);
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_apple_trailers_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_AUTHOR,
+ GRL_METADATA_KEY_DATE,
+ GRL_METADATA_KEY_DESCRIPTION,
+ GRL_METADATA_KEY_DURATION,
+ GRL_METADATA_KEY_GENRE,
+ GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_THUMBNAIL,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_CERTIFICATE,
+ GRL_METADATA_KEY_STUDIO,
+ GRL_METADATA_KEY_LICENSE,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_apple_trailers_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ GrlAppleTrailersSource *at_source = GRL_APPLE_TRAILERS_SOURCE (source);
+ OperationData *op_data;
+
+ GRL_DEBUG ("grl_apple_trailers_source_browse");
+
+ op_data = g_slice_new0 (OperationData);
+ op_data->bs = bs;
+ grl_media_source_set_operation_data (source, bs->browse_id, op_data);
+
+ if (at_source->priv->hd) {
+ read_url_async (at_source, APPLE_TRAILERS_CURRENT_HD, op_data);
+ } else {
+ read_url_async (at_source, APPLE_TRAILERS_CURRENT_SD, op_data);
+ }
+}
+
+static void
+grl_apple_trailers_source_cancel (GrlMediaSource *source, guint operation_id)
+{
+ OperationData *op_data;
+ GrlAppleTrailersSourcePriv *priv;
+
+ GRL_DEBUG ("grl_apple_trailers_source_cancel");
+
+ priv = GRL_APPLE_TRAILERS_SOURCE_GET_PRIVATE (source);
+ if (priv->cancellable && G_IS_CANCELLABLE (priv->cancellable))
+ g_cancellable_cancel (priv->cancellable);
+ priv->cancellable = NULL;
+
+ if (priv->wc)
+ grl_net_wc_flush_delayed_requests (priv->wc);
+
+ op_data = (OperationData *) grl_media_source_get_operation_data (source, operation_id);
+
+ if (op_data) {
+ op_data->cancelled = TRUE;
+ }
+}
diff --git a/src/media/apple-trailers/grl-apple-trailers.h b/src/media/apple-trailers/grl-apple-trailers.h
new file mode 100644
index 0000000..74f8f9a
--- /dev/null
+++ b/src/media/apple-trailers/grl-apple-trailers.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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_APPLE_TRAILERS_SOURCE_H_
+#define _GRL_APPLE_TRAILERS_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_APPLE_TRAILERS_SOURCE_TYPE \
+ (grl_apple_trailers_source_get_type ())
+
+#define GRL_APPLE_TRAILERS_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_APPLE_TRAILERS_SOURCE_TYPE, \
+ GrlAppleTrailersSource))
+
+#define GRL_IS_APPLE_TRAILERS_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_APPLE_TRAILERS_SOURCE_TYPE))
+
+#define GRL_APPLE_TRAILERS_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_APPLE_TRAILERS_SOURCE_TYPE, \
+ GrlAppleTrailersSourceClass))
+
+#define GRL_IS_APPLE_TRAILERS_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_APPLE_TRAILERS_SOURCE_TYPE))
+
+#define GRL_APPLE_TRAILERS_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_APPLE_TRAILERS_SOURCE_TYPE, \
+ GrlAppleTrailersSourceClass))
+
+typedef struct _GrlAppleTrailersSource GrlAppleTrailersSource;
+typedef struct _GrlAppleTrailersSourcePriv GrlAppleTrailersSourcePriv;
+
+struct _GrlAppleTrailersSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlAppleTrailersSourcePriv *priv;
+
+};
+
+typedef struct _GrlAppleTrailersSourceClass GrlAppleTrailersSourceClass;
+
+struct _GrlAppleTrailersSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_apple_trailers_source_get_type (void);
+
+#endif /* _GRL_APPLE_TRAILERS_SOURCE_H_ */
diff --git a/src/media/apple-trailers/grl-apple-trailers.xml b/src/media/apple-trailers/grl-apple-trailers.xml
new file mode 100644
index 0000000..9fd929a
--- /dev/null
+++ b/src/media/apple-trailers/grl-apple-trailers.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Apple Movie Trailers</name>
+ <description>A plugin for browsing Apple Movie Trailers</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/bookmarks/Makefile.am b/src/media/bookmarks/Makefile.am
new file mode 100644
index 0000000..0a031f5
--- /dev/null
+++ b/src/media/bookmarks/Makefile.am
@@ -0,0 +1,34 @@
+#
+# Makefile.am
+#
+# Author: Iago Toral Quiroga <itoral igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlbookmarks.la
+
+libgrlbookmarks_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(SQLITE_CFLAGS)
+
+libgrlbookmarks_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(SQLITE_LIBS)
+
+libgrlbookmarks_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlbookmarks_la_SOURCES = grl-bookmarks.c grl-bookmarks.h
+
+libdir=$(GRL_PLUGINS_DIR)
+bookmarksxmldir = $(GRL_PLUGINS_CONF_DIR)
+bookmarksxml_DATA = $(BOOKMARKS_PLUGIN_ID).xml
+
+EXTRA_DIST = $(bookmarksxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/bookmarks/grl-bookmarks.c b/src/media/bookmarks/grl-bookmarks.c
new file mode 100644
index 0000000..1c7de10
--- /dev/null
+++ b/src/media/bookmarks/grl-bookmarks.c
@@ -0,0 +1,888 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
+#include <sqlite3.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "grl-bookmarks.h"
+
+#define GRL_BOOKMARKS_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_BOOKMARKS_SOURCE_TYPE, \
+ GrlBookmarksPrivate))
+
+#define GRL_ROOT_TITLE "Bookmarks"
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT bookmarks_log_domain
+GRL_LOG_DOMAIN_STATIC(bookmarks_log_domain);
+
+/* --- Database --- */
+
+#define GRL_SQL_DB ".grl-bookmarks"
+
+#define GRL_SQL_CREATE_TABLE_BOOKMARKS \
+ "CREATE TABLE IF NOT EXISTS bookmarks (" \
+ "id INTEGER PRIMARY KEY AUTOINCREMENT," \
+ "parent INTEGER REFERENCES bookmarks (id)," \
+ "type INTEGER," \
+ "url TEXT," \
+ "title TEXT," \
+ "date TEXT," \
+ "mime TEXT," \
+ "desc TEXT)"
+
+#define GRL_SQL_GET_BOOKMARKS_BY_PARENT \
+ "SELECT b1.*, count(b2.parent <> '') " \
+ "FROM bookmarks b1 LEFT OUTER JOIN bookmarks b2 " \
+ " ON b1.id = b2.parent " \
+ "WHERE b1.parent='%s' " \
+ "GROUP BY b1.id " \
+ "LIMIT %u OFFSET %u"
+
+#define GRL_SQL_GET_BOOKMARK_BY_ID \
+ "SELECT b1.*, count(b2.parent <> '') " \
+ "FROM bookmarks b1 LEFT OUTER JOIN bookmarks b2 " \
+ " ON b1.id = b2.parent " \
+ "WHERE b1.id='%s' " \
+ "GROUP BY b1.id " \
+ "LIMIT 1"
+
+#define GRL_SQL_STORE_BOOKMARK \
+ "INSERT INTO bookmarks " \
+ "(parent, type, url, title, date, mime, desc) " \
+ "VALUES (?, ?, ?, ?, ?, ?, ?)"
+
+#define GRL_SQL_REMOVE_BOOKMARK \
+ "DELETE FROM bookmarks " \
+ "WHERE id='%s' or parent='%s'"
+
+#define GRL_SQL_REMOVE_ORPHAN \
+ "DELETE FROM bookmarks " \
+ "WHERE id in ( " \
+ " SELECT DISTINCT id FROM bookmarks " \
+ " WHERE parent NOT IN ( " \
+ " SELECT DISTINCT id FROM bookmarks) " \
+ " and parent <> 0)"
+
+#define GRL_SQL_GET_BOOKMARKS_BY_TEXT \
+ "SELECT b1.*, count(b2.parent <> '') " \
+ "FROM bookmarks b1 LEFT OUTER JOIN bookmarks b2 " \
+ " ON b1.id = b2.parent " \
+ "WHERE (b1.title LIKE '%%%s%%' OR b1.desc LIKE '%%%s%%') " \
+ " AND b1.type = 1 " \
+ "GROUP BY b1.id " \
+ "LIMIT %u OFFSET %u"
+
+#define GRL_SQL_GET_BOOKMARKS_BY_QUERY \
+ "SELECT b1.*, count(b2.parent <> '') " \
+ "FROM bookmarks b1 LEFT OUTER JOIN bookmarks b2 " \
+ " ON b1.id = b2.parent " \
+ "WHERE %s " \
+ "GROUP BY b1.id " \
+ "LIMIT %u OFFSET %u"
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID BOOKMARKS_PLUGIN_ID
+
+#define SOURCE_ID "grl-bookmarks"
+#define SOURCE_NAME "Bookmarks"
+#define SOURCE_DESC "A source for organizing media bookmarks"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+
+enum {
+ BOOKMARK_TYPE_CATEGORY = 0,
+ BOOKMARK_TYPE_STREAM,
+};
+
+enum {
+ BOOKMARK_ID = 0,
+ BOOKMARK_PARENT,
+ BOOKMARK_TYPE,
+ BOOKMARK_URL,
+ BOOKMARK_TITLE,
+ BOOKMARK_DATE,
+ BOOKMARK_MIME,
+ BOOKMARK_DESC,
+ BOOKMARK_CHILDCOUNT
+};
+
+struct _GrlBookmarksPrivate {
+ sqlite3 *db;
+ gboolean notify_changes;
+};
+
+typedef struct {
+ GrlMediaSource *source;
+ guint operation_id;
+ const gchar *media_id;
+ guint skip;
+ guint count;
+ GrlMediaSourceResultCb callback;
+ guint error_code;
+ gboolean is_query;
+ gpointer user_data;
+} OperationSpec;
+
+static GrlBookmarksSource *grl_bookmarks_source_new (void);
+
+static void grl_bookmarks_source_finalize (GObject *plugin);
+
+static const GList *grl_bookmarks_source_supported_keys (GrlMetadataSource *source);
+static GrlSupportedOps grl_bookmarks_source_supported_operations (GrlMetadataSource *metadata_source);
+
+static void grl_bookmarks_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+static void grl_bookmarks_source_query (GrlMediaSource *source,
+ GrlMediaSourceQuerySpec *qs);
+static void grl_bookmarks_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+static void grl_bookmarks_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms);
+static void grl_bookmarks_source_store (GrlMediaSource *source,
+ GrlMediaSourceStoreSpec *ss);
+static void grl_bookmarks_source_remove (GrlMediaSource *source,
+ GrlMediaSourceRemoveSpec *rs);
+
+static gboolean grl_bookmarks_source_notify_change_start (GrlMediaSource *source,
+ GError **error);
+
+static gboolean grl_bookmarks_source_notify_change_stop (GrlMediaSource *source,
+ GError **error);
+
+ /* =================== Bookmarks Plugin =============== */
+
+ static gboolean
+ grl_bookmarks_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+ {
+ GRL_LOG_DOMAIN_INIT (bookmarks_log_domain, "bookmarks");
+
+ GRL_DEBUG ("grl_bookmarks_plugin_init");
+
+ GrlBookmarksSource *source = grl_bookmarks_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+ }
+
+ GRL_PLUGIN_REGISTER (grl_bookmarks_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+ /* ================== Bookmarks GObject ================ */
+
+ static GrlBookmarksSource *
+ grl_bookmarks_source_new (void)
+ {
+ GRL_DEBUG ("grl_bookmarks_source_new");
+ return g_object_new (GRL_BOOKMARKS_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+ }
+
+ static void
+ grl_bookmarks_source_class_init (GrlBookmarksSourceClass * klass)
+ {
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+
+ gobject_class->finalize = grl_bookmarks_source_finalize;
+
+ metadata_class->supported_operations =
+ grl_bookmarks_source_supported_operations;
+
+ source_class->browse = grl_bookmarks_source_browse;
+ source_class->search = grl_bookmarks_source_search;
+ source_class->query = grl_bookmarks_source_query;
+ source_class->store = grl_bookmarks_source_store;
+ source_class->remove = grl_bookmarks_source_remove;
+ source_class->metadata = grl_bookmarks_source_metadata;
+ source_class->notify_change_start = grl_bookmarks_source_notify_change_start;
+ source_class->notify_change_stop = grl_bookmarks_source_notify_change_stop;
+
+ metadata_class->supported_keys = grl_bookmarks_source_supported_keys;
+
+ g_type_class_add_private (klass, sizeof (GrlBookmarksPrivate));
+}
+
+static void
+grl_bookmarks_source_init (GrlBookmarksSource *source)
+{
+ gint r;
+ const gchar *home;
+ gchar *db_path;
+ gchar *sql_error = NULL;
+
+ source->priv = GRL_BOOKMARKS_GET_PRIVATE (source);
+
+ home = g_getenv ("HOME");
+ if (!home) {
+ GRL_WARNING ("$HOME not set, cannot open database");
+ return;
+ }
+
+ GRL_DEBUG ("Opening database connection...");
+ db_path = g_strconcat (home, G_DIR_SEPARATOR_S, GRL_SQL_DB, NULL);
+ r = sqlite3_open (db_path, &source->priv->db);
+ if (r) {
+ g_critical ("Failed to open database '%s': %s",
+ db_path, sqlite3_errmsg (source->priv->db));
+ sqlite3_close (source->priv->db);
+ return;
+ }
+ GRL_DEBUG (" OK");
+
+ GRL_DEBUG ("Checking database tables...");
+ r = sqlite3_exec (source->priv->db, GRL_SQL_CREATE_TABLE_BOOKMARKS,
+ NULL, NULL, &sql_error);
+
+ if (r) {
+ if (sql_error) {
+ GRL_WARNING ("Failed to create database tables: %s", sql_error);
+ sqlite3_free (sql_error);
+ sql_error = NULL;
+ } else {
+ GRL_WARNING ("Failed to create database tables.");
+ }
+ sqlite3_close (source->priv->db);
+ return;
+ }
+ GRL_DEBUG (" OK");
+
+ g_free (db_path);
+}
+
+G_DEFINE_TYPE (GrlBookmarksSource, grl_bookmarks_source, GRL_TYPE_MEDIA_SOURCE);
+
+static void
+grl_bookmarks_source_finalize (GObject *object)
+{
+ GrlBookmarksSource *source;
+
+ GRL_DEBUG ("grl_bookmarks_source_finalize");
+
+ source = GRL_BOOKMARKS_SOURCE (object);
+
+ sqlite3_close (source->priv->db);
+
+ G_OBJECT_CLASS (grl_bookmarks_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static gboolean
+mime_is_video (const gchar *mime)
+{
+ return mime && strstr (mime, "video") != NULL;
+}
+
+static gboolean
+mime_is_audio (const gchar *mime)
+{
+ return mime && strstr (mime, "audio") != NULL;
+}
+
+static GrlMedia *
+build_media_from_stmt (GrlMedia *content, sqlite3_stmt *sql_stmt)
+{
+ GrlMedia *media = NULL;
+ gchar *id;
+ gchar *title;
+ gchar *url;
+ gchar *desc;
+ gchar *date;
+ gchar *mime;
+ guint type;
+ guint childcount;
+
+ if (content) {
+ media = content;
+ }
+
+ id = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_ID);
+ title = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_TITLE);
+ url = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_URL);
+ desc = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_DESC);
+ date = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_DATE);
+ mime = (gchar *) sqlite3_column_text (sql_stmt, BOOKMARK_MIME);
+ type = (guint) sqlite3_column_int (sql_stmt, BOOKMARK_TYPE);
+ childcount = (guint) sqlite3_column_int (sql_stmt, BOOKMARK_CHILDCOUNT);
+
+ if (!media) {
+ if (type == BOOKMARK_TYPE_CATEGORY) {
+ media = GRL_MEDIA (grl_media_box_new ());
+ } else if (mime_is_audio (mime)) {
+ media = GRL_MEDIA (grl_media_new ());
+ } else if (mime_is_video (mime)) {
+ media = GRL_MEDIA (grl_media_new ());
+ } else {
+ media = GRL_MEDIA (grl_media_new ());
+ }
+ }
+
+ grl_media_set_id (media, id);
+ grl_media_set_title (media, title);
+ if (url) {
+ grl_media_set_url (media, url);
+ }
+ if (desc) {
+ grl_media_set_description (media, desc);
+ }
+ if (date) {
+ grl_media_set_date (media, date);
+ }
+
+ if (type == BOOKMARK_TYPE_CATEGORY) {
+ grl_media_box_set_childcount (GRL_MEDIA_BOX (media), childcount);
+ }
+
+ return media;
+}
+
+static void
+bookmark_metadata (GrlMediaSourceMetadataSpec *ms)
+{
+ gint r;
+ sqlite3_stmt *sql_stmt = NULL;
+ sqlite3 *db;
+ GError *error = NULL;
+ gchar *sql;
+ const gchar *id;
+
+ GRL_DEBUG ("bookmark_metadata");
+
+ db = GRL_BOOKMARKS_SOURCE (ms->source)->priv->db;
+
+ id = grl_media_get_id (ms->media);
+ if (!id) {
+ /* Root category: special case */
+ grl_media_set_title (ms->media, GRL_ROOT_TITLE);
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ return;
+ }
+
+ sql = g_strdup_printf (GRL_SQL_GET_BOOKMARK_BY_ID, id);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to get bookmark: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Failed to get bookmark metadata");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ if (r == SQLITE_ROW) {
+ build_media_from_stmt (ms->media, sql_stmt);
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ } else {
+ GRL_WARNING ("Failed to get bookmark: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Failed to get bookmark metadata");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ }
+
+ sqlite3_finalize (sql_stmt);
+}
+
+static void
+produce_bookmarks_from_sql (OperationSpec *os, const gchar *sql)
+{
+ gint r;
+ sqlite3_stmt *sql_stmt = NULL;
+ sqlite3 *db;
+ GrlMedia *media;
+ GError *error = NULL;
+ GList *medias = NULL;
+ guint count = 0;
+ GList *iter;
+
+ GRL_DEBUG ("produce_bookmarks_from_sql");
+
+ GRL_DEBUG ("%s", sql);
+ db = GRL_BOOKMARKS_SOURCE (os->source)->priv->db;
+ r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to retrieve bookmarks: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to retrieve bookmarks list");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+ goto free_resources;
+ }
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ while (r == SQLITE_ROW) {
+ media = build_media_from_stmt (NULL, sql_stmt);
+ medias = g_list_prepend (medias, media);
+ count++;
+ r = sqlite3_step (sql_stmt);
+ }
+
+ if (r != SQLITE_DONE) {
+ GRL_WARNING ("Failed to retrieve bookmarks: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to retrieve bookmarks list");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+ goto free_resources;
+ }
+
+ if (count > 0) {
+ medias = g_list_reverse (medias);
+ iter = medias;
+ while (iter) {
+ media = GRL_MEDIA (iter->data);
+ os->callback (os->source,
+ os->operation_id,
+ media,
+ --count,
+ os->user_data,
+ NULL);
+ iter = g_list_next (iter);
+ }
+ g_list_free (medias);
+ } else {
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
+ }
+
+ free_resources:
+ if (sql_stmt)
+ sqlite3_finalize (sql_stmt);
+}
+
+static void
+produce_bookmarks_by_query (OperationSpec *os, const gchar *query)
+{
+ gchar *sql;
+ GRL_DEBUG ("produce_bookmarks_by_query");
+ sql = g_strdup_printf (GRL_SQL_GET_BOOKMARKS_BY_QUERY,
+ query, os->count, os->skip);
+ produce_bookmarks_from_sql (os, sql);
+ g_free (sql);
+}
+
+static void
+produce_bookmarks_by_text (OperationSpec *os, const gchar *text)
+{
+ gchar *sql;
+ GRL_DEBUG ("produce_bookmarks_by_text");
+ sql = g_strdup_printf (GRL_SQL_GET_BOOKMARKS_BY_TEXT,
+ text? text: "",
+ text? text: "",
+ os->count,
+ os->skip);
+ produce_bookmarks_from_sql (os, sql);
+ g_free (sql);
+}
+
+static void
+produce_bookmarks_from_category (OperationSpec *os, const gchar *category_id)
+{
+ gchar *sql;
+ GRL_DEBUG ("produce_bookmarks_from_category");
+ sql = g_strdup_printf (GRL_SQL_GET_BOOKMARKS_BY_PARENT,
+ category_id, os->count, os->skip);
+ produce_bookmarks_from_sql (os, sql);
+ g_free (sql);
+}
+
+static void
+remove_bookmark (GrlBookmarksSource *bookmarks_source,
+ const gchar *bookmark_id,
+ GError **error)
+{
+ gint r;
+ gchar *sql_error;
+ gchar *sql;
+
+ GRL_DEBUG ("remove_bookmark");
+
+ sql = g_strdup_printf (GRL_SQL_REMOVE_BOOKMARK, bookmark_id, bookmark_id);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_exec (bookmarks_source->priv->db, sql, NULL, NULL, &sql_error);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to remove bookmark '%s': %s", bookmark_id, sql_error);
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_REMOVE_FAILED,
+ "Failed to remove bookmark");
+ sqlite3_free (sql_error);
+ }
+
+ /* Remove orphan nodes from database */
+ GRL_DEBUG ("%s", GRL_SQL_REMOVE_ORPHAN);
+ r = sqlite3_exec (bookmarks_source->priv->db,
+ GRL_SQL_REMOVE_ORPHAN,
+ NULL, NULL, NULL);
+
+ if (bookmarks_source->priv->notify_changes) {
+ /* We can improve accuracy computing the parent container of removed
+ element */
+ grl_media_source_notify_change (GRL_MEDIA_SOURCE (bookmarks_source),
+ NULL,
+ GRL_CONTENT_REMOVED,
+ TRUE);
+ }
+}
+
+static void
+store_bookmark (GrlBookmarksSource *bookmarks_source,
+ GrlMediaBox *parent,
+ GrlMedia *bookmark,
+ GError **error)
+{
+ gint r;
+ sqlite3_stmt *sql_stmt = NULL;
+ const gchar *title;
+ const gchar *url;
+ const gchar *desc;
+ GTimeVal now;
+ const gchar *parent_id;
+ const gchar *mime;
+ gchar *date;
+ guint type;
+ gchar *id;
+
+ GRL_DEBUG ("store_bookmark");
+
+ title = grl_media_get_title (bookmark);
+ url = grl_media_get_url (bookmark);
+ desc = grl_media_get_description (bookmark);
+ mime = grl_media_get_mime (bookmark);
+ g_get_current_time (&now);
+ date = g_time_val_to_iso8601 (&now);
+
+ if (!parent) {
+ parent_id = "0";
+ } else {
+ parent_id = grl_media_get_id (GRL_MEDIA (parent));
+ }
+ if (!parent_id) {
+ parent_id = "0";
+ }
+
+ GRL_DEBUG ("%s", GRL_SQL_STORE_BOOKMARK);
+ r = sqlite3_prepare_v2 (bookmarks_source->priv->db,
+ GRL_SQL_STORE_BOOKMARK,
+ strlen (GRL_SQL_STORE_BOOKMARK),
+ &sql_stmt, NULL);
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to store bookmark '%s': %s", title,
+ sqlite3_errmsg (bookmarks_source->priv->db));
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_STORE_FAILED,
+ "Failed to store bookmark '%s'", title);
+ return;
+ }
+
+ GRL_DEBUG ("URL: '%s'", url);
+
+ if (GRL_IS_MEDIA_BOX (bookmark)) {
+ type = BOOKMARK_TYPE_CATEGORY;
+ } else {
+ type = BOOKMARK_TYPE_STREAM;
+ }
+
+ sqlite3_bind_text (sql_stmt, BOOKMARK_PARENT, parent_id, -1, SQLITE_STATIC);
+ sqlite3_bind_int (sql_stmt, BOOKMARK_TYPE, type);
+ if (type == BOOKMARK_TYPE_STREAM) {
+ sqlite3_bind_text (sql_stmt, BOOKMARK_URL, url, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null (sql_stmt, BOOKMARK_URL);
+ }
+ sqlite3_bind_text (sql_stmt, BOOKMARK_TITLE, title, -1, SQLITE_STATIC);
+ if (date) {
+ sqlite3_bind_text (sql_stmt, BOOKMARK_DATE, date, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null (sql_stmt, BOOKMARK_DATE);
+ }
+ if (mime) {
+ sqlite3_bind_text (sql_stmt, BOOKMARK_MIME, mime, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null (sql_stmt, BOOKMARK_MIME);
+ }
+ if (desc) {
+ sqlite3_bind_text (sql_stmt, BOOKMARK_DESC, desc, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_null (sql_stmt, BOOKMARK_DESC);
+ }
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ if (r != SQLITE_DONE) {
+ GRL_WARNING ("Failed to store bookmark '%s': %s", title,
+ sqlite3_errmsg (bookmarks_source->priv->db));
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_STORE_FAILED,
+ "Failed to store bookmark '%s'", title);
+ sqlite3_finalize (sql_stmt);
+ return;
+ }
+
+ sqlite3_finalize (sql_stmt);
+
+ id = g_strdup_printf ("%llu",
+ sqlite3_last_insert_rowid (bookmarks_source->priv->db));
+ grl_media_set_id (bookmark, id);
+ g_free (id);
+
+ if (bookmarks_source->priv->notify_changes) {
+ grl_media_source_notify_change (GRL_MEDIA_SOURCE (bookmarks_source),
+ GRL_MEDIA (parent),
+ GRL_CONTENT_ADDED,
+ FALSE);
+ }
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_bookmarks_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_CHILDCOUNT,
+ GRL_METADATA_KEY_DESCRIPTION,
+ GRL_METADATA_KEY_DATE,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_bookmarks_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ GRL_DEBUG ("grl_bookmarks_source_browse");
+
+ OperationSpec *os;
+ GrlBookmarksSource *bookmarks_source;
+ GError *error = NULL;
+
+ bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
+ if (!bookmarks_source->priv->db) {
+ GRL_WARNING ("Can't execute operation: no database connection.");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "No database connection");
+ bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, error);
+ g_error_free (error);
+ }
+
+ /* Configure browse operation */
+ os = g_slice_new0 (OperationSpec);
+ os->source = bs->source;
+ os->operation_id = bs->browse_id;
+ os->media_id = grl_media_get_id (bs->container);
+ os->count = bs->count;
+ os->skip = bs->skip;
+ os->callback = bs->callback;
+ os->user_data = bs->user_data;
+ os->error_code = GRL_CORE_ERROR_BROWSE_FAILED;
+
+ produce_bookmarks_from_category (os, os->media_id ? os->media_id : "0");
+ g_slice_free (OperationSpec, os);
+}
+
+static void
+grl_bookmarks_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss)
+{
+ GRL_DEBUG ("grl_bookmarks_source_search");
+
+ GrlBookmarksSource *bookmarks_source;
+ OperationSpec *os;
+ GError *error = NULL;
+
+ bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
+ if (!bookmarks_source->priv->db) {
+ GRL_WARNING ("Can't execute operation: no database connection.");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_QUERY_FAILED,
+ "No database connection");
+ ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, error);
+ g_error_free (error);
+ }
+
+ os = g_slice_new0 (OperationSpec);
+ os->source = ss->source;
+ os->operation_id = ss->search_id;
+ os->count = ss->count;
+ os->skip = ss->skip;
+ os->callback = ss->callback;
+ os->user_data = ss->user_data;
+ os->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
+ produce_bookmarks_by_text (os, ss->text);
+ g_slice_free (OperationSpec, os);
+}
+
+static void
+grl_bookmarks_source_query (GrlMediaSource *source,
+ GrlMediaSourceQuerySpec *qs)
+{
+ GRL_DEBUG ("grl_bookmarks_source_query");
+
+ GrlBookmarksSource *bookmarks_source;
+ OperationSpec *os;
+ GError *error = NULL;
+
+ bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
+ if (!bookmarks_source->priv->db) {
+ GRL_WARNING ("Can't execute operation: no database connection.");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_QUERY_FAILED,
+ "No database connection");
+ qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
+ g_error_free (error);
+ }
+
+ os = g_slice_new0 (OperationSpec);
+ os->source = qs->source;
+ os->operation_id = qs->query_id;
+ os->count = qs->count;
+ os->skip = qs->skip;
+ os->callback = qs->callback;
+ os->user_data = qs->user_data;
+ os->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
+ produce_bookmarks_by_query (os, qs->query);
+ g_slice_free (OperationSpec, os);
+}
+
+static void
+grl_bookmarks_source_store (GrlMediaSource *source, GrlMediaSourceStoreSpec *ss)
+{
+ GRL_DEBUG ("grl_bookmarks_source_store");
+ /* FIXME: Try to guess bookmark mime somehow */
+ GError *error = NULL;
+ store_bookmark (GRL_BOOKMARKS_SOURCE (ss->source),
+ ss->parent, ss->media, &error);
+ ss->callback (ss->source, ss->parent, ss->media, ss->user_data, error);
+ if (error) {
+ g_error_free (error);
+ }
+}
+
+static void grl_bookmarks_source_remove (GrlMediaSource *source,
+ GrlMediaSourceRemoveSpec *rs)
+{
+ GRL_DEBUG ("grl_bookmarks_source_remove");
+ GError *error = NULL;
+ remove_bookmark (GRL_BOOKMARKS_SOURCE (rs->source),
+ rs->media_id, &error);
+ rs->callback (rs->source, rs->media, rs->user_data, error);
+ if (error) {
+ g_error_free (error);
+ }
+}
+
+static void
+grl_bookmarks_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ GRL_DEBUG ("grl_bookmarks_source_metadata");
+
+ GrlBookmarksSource *bookmarks_source;
+ GError *error = NULL;
+
+ bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
+ if (!bookmarks_source->priv->db) {
+ GRL_WARNING ("Can't execute operation: no database connection.");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "No database connection");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ }
+
+ bookmark_metadata (ms);
+}
+
+static GrlSupportedOps
+grl_bookmarks_source_supported_operations (GrlMetadataSource *metadata_source)
+{
+ GrlSupportedOps caps;
+ GrlBookmarksSource *source;
+
+ source = GRL_BOOKMARKS_SOURCE (metadata_source);
+ caps = GRL_OP_BROWSE | GRL_OP_METADATA | GRL_OP_SEARCH | GRL_OP_QUERY |
+ GRL_OP_STORE | GRL_OP_STORE_PARENT | GRL_OP_REMOVE | GRL_OP_NOTIFY_CHANGE;
+
+ return caps;
+}
+
+static gboolean
+grl_bookmarks_source_notify_change_start (GrlMediaSource *source,
+ GError **error)
+{
+ GrlBookmarksSource *bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
+
+ bookmarks_source->priv->notify_changes = TRUE;
+
+ return TRUE;
+}
+
+static gboolean
+grl_bookmarks_source_notify_change_stop (GrlMediaSource *source,
+ GError **error)
+{
+ GrlBookmarksSource *bookmarks_source = GRL_BOOKMARKS_SOURCE (source);
+
+ bookmarks_source->priv->notify_changes = FALSE;
+
+ return TRUE;
+}
diff --git a/src/media/bookmarks/grl-bookmarks.h b/src/media/bookmarks/grl-bookmarks.h
new file mode 100644
index 0000000..b5529b1
--- /dev/null
+++ b/src/media/bookmarks/grl-bookmarks.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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_BOOKMARKS_SOURCE_H_
+#define _GRL_BOOKMARKS_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_BOOKMARKS_SOURCE_TYPE \
+ (grl_bookmarks_source_get_type ())
+
+#define GRL_BOOKMARKS_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_BOOKMARKS_SOURCE_TYPE, \
+ GrlBookmarksSource))
+
+#define GRL_IS_BOOKMARKS_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_BOOKMARKS_SOURCE_TYPE))
+
+#define GRL_BOOKMARKS_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_BOOKMARKS_SOURCE_TYPE, \
+ GrlBookmarksSourceClass))
+
+#define GRL_IS_BOOKMARKS_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_BOOKMARKS_SOURCE_TYPE))
+
+#define GRL_BOOKMARKS_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_BOOKMARKS_SOURCE_TYPE, \
+ GrlBookmarksSourceClass))
+
+typedef struct _GrlBookmarksPrivate GrlBookmarksPrivate;
+typedef struct _GrlBookmarksSource GrlBookmarksSource;
+
+struct _GrlBookmarksSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlBookmarksPrivate *priv;
+};
+
+typedef struct _GrlBookmarksSourceClass GrlBookmarksSourceClass;
+
+struct _GrlBookmarksSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_bookmarks_source_get_type (void);
+
+#endif /* _GRL_BOOKMARKS_SOURCE_H_ */
diff --git a/src/media/bookmarks/grl-bookmarks.xml b/src/media/bookmarks/grl-bookmarks.xml
new file mode 100644
index 0000000..74ef3ee
--- /dev/null
+++ b/src/media/bookmarks/grl-bookmarks.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Bookmarks</name>
+ <description>A plugin for organizing media bookmarks</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/filesystem/Makefile.am b/src/media/filesystem/Makefile.am
new file mode 100644
index 0000000..c685b85
--- /dev/null
+++ b/src/media/filesystem/Makefile.am
@@ -0,0 +1,34 @@
+#
+# Makefile.am
+#
+# Author: Iago Toral Quiroga <itoral igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlfilesystem.la
+
+libgrlfilesystem_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GIO_CFLAGS)
+
+libgrlfilesystem_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GIO_LIBS)
+
+libgrlfilesystem_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlfilesystem_la_SOURCES = grl-filesystem.c grl-filesystem.h
+
+libdir=$(GRL_PLUGINS_DIR)
+filesystemxmldir = $(GRL_PLUGINS_CONF_DIR)
+filesystemxml_DATA = $(FILESYSTEM_PLUGIN_ID).xml
+
+EXTRA_DIST = $(filesystemxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/filesystem/TODO b/src/media/filesystem/TODO
new file mode 100644
index 0000000..fb6c8ed
--- /dev/null
+++ b/src/media/filesystem/TODO
@@ -0,0 +1,2 @@
+- childcount for categories does not filter media-only files
+ - maybe this is acceptable (since this way it has a performance benefot)
diff --git a/src/media/filesystem/grl-filesystem.c b/src/media/filesystem/grl-filesystem.c
new file mode 100644
index 0000000..d4fe60c
--- /dev/null
+++ b/src/media/filesystem/grl-filesystem.c
@@ -0,0 +1,1343 @@
+/*
+ * Copyright (C) 2010, 2011 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
+#include <gio/gio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "grl-filesystem.h"
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT filesystem_log_domain
+GRL_LOG_DOMAIN_STATIC(filesystem_log_domain);
+
+/* -------- File info ------- */
+
+#define FILE_ATTRIBUTES \
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
+ G_FILE_ATTRIBUTE_TIME_MODIFIED "," \
+ G_FILE_ATTRIBUTE_THUMBNAIL_PATH "," \
+ G_FILE_ATTRIBUTE_THUMBNAILING_FAILED
+
+#define FILE_ATTRIBUTES_FAST \
+ G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN
+
+/* ---- Emission chunks ----- */
+
+#define BROWSE_IDLE_CHUNK_SIZE 5
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID FILESYSTEM_PLUGIN_ID
+
+#define SOURCE_ID "grl-filesystem"
+#define SOURCE_NAME "Filesystem"
+#define SOURCE_DESC "A source for browsing the filesystem"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+/* --- Grilo Filesystem Private --- */
+
+#define GRL_FILESYSTEM_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_FILESYSTEM_SOURCE_TYPE, \
+ GrlFilesystemSourcePrivate))
+
+struct _GrlFilesystemSourcePrivate {
+ GList *chosen_paths;
+ guint max_search_depth;
+ /* a mapping operation_id -> GCancellable to cancel this operation */
+ GHashTable *cancellables;
+ /* Monitors for changes in directories */
+ GList *monitors;
+ GCancellable *cancellable_monitors;
+};
+
+/* --- Data types --- */
+
+typedef struct _RecursiveOperation RecursiveOperation;
+
+typedef gboolean (*RecursiveOperationCb) (GFileInfo *file_info,
+ RecursiveOperation *operation);
+
+typedef struct {
+ GrlMediaSourceBrowseSpec *spec;
+ GList *entries;
+ GList *current;
+ const gchar *path;
+ guint remaining;
+ GCancellable *cancellable;
+ guint id;
+} BrowseIdleData;
+
+struct _RecursiveOperation {
+ RecursiveOperationCb on_cancel;
+ RecursiveOperationCb on_finish;
+ RecursiveOperationCb on_dir;
+ RecursiveOperationCb on_file;
+ gpointer on_dir_data;
+ gpointer on_file_data;
+ GCancellable *cancellable;
+ GQueue *directories;
+ guint max_depth;
+};
+
+typedef struct {
+ guint depth;
+ GFile *directory;
+} RecursiveEntry;
+
+
+static GrlFilesystemSource *grl_filesystem_source_new (void);
+
+static void grl_filesystem_source_finalize (GObject *object);
+
+gboolean grl_filesystem_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+static const GList *grl_filesystem_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_filesystem_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms);
+
+static void grl_filesystem_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+
+static void grl_filesystem_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+
+
+static gboolean grl_filesystem_test_media_from_uri (GrlMediaSource *source,
+ const gchar *uri);
+
+static void grl_filesystem_get_media_from_uri (GrlMediaSource *source,
+ GrlMediaSourceMediaFromUriSpec *mfus);
+
+static void grl_filesystem_source_cancel (GrlMediaSource *source,
+ guint operation_id);
+
+static gboolean grl_filesystem_source_notify_change_start (GrlMediaSource *source,
+ GError **error);
+
+static gboolean grl_filesystem_source_notify_change_stop (GrlMediaSource *source,
+ GError **error);
+
+/* =================== Filesystem Plugin =============== */
+
+gboolean
+grl_filesystem_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GrlConfig *config;
+ GList *chosen_paths = NULL;
+ guint max_search_depth = GRILO_CONF_MAX_SEARCH_DEPTH_DEFAULT;
+
+ GRL_LOG_DOMAIN_INIT (filesystem_log_domain, "filesystem");
+
+ GRL_DEBUG ("filesystem_plugin_init");
+
+ GrlFilesystemSource *source = grl_filesystem_source_new ();
+
+ for (; configs; configs = g_list_next (configs)) {
+ gchar *path;
+ config = GRL_CONFIG (configs->data);
+ path = grl_config_get_string (config, GRILO_CONF_CHOSEN_PATH);
+ if (path) {
+ chosen_paths = g_list_append (chosen_paths, path);
+ }
+ if (grl_config_has_param (config, GRILO_CONF_MAX_SEARCH_DEPTH)) {
+ max_search_depth = (guint)grl_config_get_int (config, GRILO_CONF_MAX_SEARCH_DEPTH);
+ }
+ }
+ source->priv->chosen_paths = chosen_paths;
+ source->priv->max_search_depth = max_search_depth;
+
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_filesystem_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Filesystem GObject ================ */
+
+
+G_DEFINE_TYPE (GrlFilesystemSource,
+ grl_filesystem_source,
+ GRL_TYPE_MEDIA_SOURCE);
+
+static GrlFilesystemSource *
+grl_filesystem_source_new (void)
+{
+ GRL_DEBUG ("grl_filesystem_source_new");
+ return g_object_new (GRL_FILESYSTEM_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_filesystem_source_class_init (GrlFilesystemSourceClass * klass)
+{
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ source_class->browse = grl_filesystem_source_browse;
+ source_class->search = grl_filesystem_source_search;
+ source_class->cancel = grl_filesystem_source_cancel;
+ source_class->notify_change_start = grl_filesystem_source_notify_change_start;
+ source_class->notify_change_stop = grl_filesystem_source_notify_change_stop;
+ source_class->metadata = grl_filesystem_source_metadata;
+ source_class->test_media_from_uri = grl_filesystem_test_media_from_uri;
+ source_class->media_from_uri = grl_filesystem_get_media_from_uri;
+ G_OBJECT_CLASS (source_class)->finalize = grl_filesystem_source_finalize;
+ metadata_class->supported_keys = grl_filesystem_source_supported_keys;
+ g_type_class_add_private (klass, sizeof (GrlFilesystemSourcePrivate));
+}
+
+static void
+grl_filesystem_source_init (GrlFilesystemSource *source)
+{
+ source->priv = GRL_FILESYSTEM_SOURCE_GET_PRIVATE (source);
+ source->priv->cancellables = g_hash_table_new (NULL, NULL);
+}
+
+static void
+grl_filesystem_source_finalize (GObject *object)
+{
+ GrlFilesystemSource *filesystem_source = GRL_FILESYSTEM_SOURCE (object);
+ g_list_foreach (filesystem_source->priv->chosen_paths, (GFunc) g_free, NULL);
+ g_list_free (filesystem_source->priv->chosen_paths);
+ g_hash_table_unref (filesystem_source->priv->cancellables);
+ G_OBJECT_CLASS (grl_filesystem_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static void recursive_operation_next_entry (RecursiveOperation *operation);
+static void add_monitor (GrlFilesystemSource *fs_source, GFile *dir);
+static void cancel_monitors (GrlFilesystemSource *fs_source);
+
+static gboolean
+mime_is_video (const gchar *mime)
+{
+ return strstr (mime, "video") != NULL;
+}
+
+static gboolean
+mime_is_audio (const gchar *mime)
+{
+ return strstr (mime, "audio") != NULL;
+}
+
+static gboolean
+mime_is_image (const gchar *mime)
+{
+ return strstr (mime, "image") != NULL;
+}
+
+static gboolean
+mime_is_media (const gchar *mime)
+{
+ if (!mime)
+ return FALSE;
+ if (!strcmp (mime, "inode/directory"))
+ return TRUE;
+ if (mime_is_audio (mime))
+ return TRUE;
+ if (mime_is_video (mime))
+ return TRUE;
+ if (mime_is_image (mime))
+ return TRUE;
+ return FALSE;
+}
+
+static gboolean
+file_is_valid_content (const gchar *path, gboolean fast)
+{
+ const gchar *mime;
+ GError *error = NULL;
+ gboolean is_media;
+ GFile *file;
+ GFileInfo *info;
+ GFileType type;
+ const gchar *spec;
+
+ if (fast) {
+ spec = FILE_ATTRIBUTES_FAST;
+ } else {
+ spec = FILE_ATTRIBUTES;
+ }
+
+ file = g_file_new_for_path (path);
+ info = g_file_query_info (file, spec, 0, NULL, &error);
+ if (error) {
+ GRL_WARNING ("Failed to get attributes for file '%s': %s",
+ path, error->message);
+ g_error_free (error);
+ g_object_unref (file);
+ return FALSE;
+ } else {
+ if (g_file_info_get_is_hidden (info)) {
+ is_media = FALSE;
+ } else {
+ if (fast) {
+ /* In fast mode we do not check mime-types,
+ any non-hidden file is accepted */
+ is_media = TRUE;
+ } else {
+ type = g_file_info_get_file_type (info);
+ mime = g_file_info_get_content_type (info);
+ if (type == G_FILE_TYPE_DIRECTORY || mime_is_media (mime)) {
+ is_media = TRUE;
+ } else {
+ is_media = FALSE;
+ }
+ }
+ }
+ g_object_unref (info);
+ g_object_unref (file);
+ return is_media;
+ }
+}
+
+static void
+set_container_childcount (const gchar *path,
+ GrlMedia *media,
+ gboolean fast)
+{
+ GDir *dir;
+ GError *error = NULL;
+ gint count;
+ const gchar *entry_name;
+
+ /* Open directory */
+ GRL_DEBUG ("Opening directory '%s' for childcount", path);
+ dir = g_dir_open (path, 0, &error);
+ if (error) {
+ GRL_WARNING ("Failed to open directory '%s': %s", path, error->message);
+ g_error_free (error);
+ return;
+ }
+
+ /* Count valid entries */
+ count = 0;
+ while ((entry_name = g_dir_read_name (dir)) != NULL) {
+ gchar *entry_path;
+ if (strcmp (path, G_DIR_SEPARATOR_S)) {
+ entry_path = g_strconcat (path, G_DIR_SEPARATOR_S, entry_name, NULL);
+ } else {
+ entry_path = g_strconcat (path, entry_name, NULL);
+ }
+ if (file_is_valid_content (entry_path, fast)) {
+ if (fast) {
+ /* in fast mode we don't compute mime-types because it is slow,
+ so we can only check if the directory is totally empty (no subdirs,
+ and no files), otherwise we just say we do not know the actual
+ childcount */
+ count = GRL_METADATA_KEY_CHILDCOUNT_UNKNOWN;
+ break;
+ }
+ count++;
+ }
+ g_free (entry_path);
+ }
+
+ g_dir_close (dir);
+
+ grl_media_box_set_childcount (GRL_MEDIA_BOX (media), count);
+}
+
+static GrlMedia *
+create_content (GrlMedia *content,
+ const gchar *path,
+ gboolean only_fast,
+ gboolean root_dir)
+{
+ GrlMedia *media = NULL;
+ gchar *str;
+ const gchar *mime;
+ GError *error = NULL;
+
+ GFile *file = g_file_new_for_path (path);
+ GFileInfo *info = g_file_query_info (file,
+ FILE_ATTRIBUTES,
+ 0,
+ NULL,
+ &error);
+
+ /* Update mode */
+ if (content) {
+ media = content;
+ }
+
+ if (error) {
+ GRL_WARNING ("Failed to get info for file '%s': %s", path,
+ error->message);
+ if (!media) {
+ media = grl_media_new ();
+ }
+
+ /* Title */
+ str = g_strrstr (path, G_DIR_SEPARATOR_S);
+ if (!str) {
+ str = (gchar *) path;
+ }
+ grl_media_set_title (media, str);
+ g_error_free (error);
+ } else {
+ mime = g_file_info_get_content_type (info);
+
+ if (!media) {
+ if (g_file_info_get_file_type (info) == G_FILE_TYPE_DIRECTORY) {
+ media = GRL_MEDIA (grl_media_box_new ());
+ } else {
+ if (mime_is_video (mime)) {
+ media = grl_media_video_new ();
+ } else if (mime_is_audio (mime)) {
+ media = grl_media_audio_new ();
+ } else if (mime_is_image (mime)) {
+ media = grl_media_image_new ();
+ } else {
+ media = grl_media_new ();
+ }
+ }
+ }
+
+ if (!GRL_IS_MEDIA_BOX (media)) {
+ grl_media_set_mime (media, mime);
+ }
+
+ /* Title */
+ str = (gchar *) g_file_info_get_display_name (info);
+ grl_media_set_title (media, str);
+
+ /* Date */
+ GTimeVal time;
+ gchar *time_str;
+ g_file_info_get_modification_time (info, &time);
+ time_str = g_time_val_to_iso8601 (&time);
+ grl_media_set_date (media, time_str);
+ g_free (time_str);
+
+ /* Thumbnail */
+ gboolean thumb_failed =
+ g_file_info_get_attribute_boolean (info,
+ G_FILE_ATTRIBUTE_THUMBNAILING_FAILED);
+ if (!thumb_failed) {
+ const gchar *thumb =
+ g_file_info_get_attribute_byte_string (info,
+ G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
+ if (thumb) {
+ gchar *thumb_uri = g_filename_to_uri (thumb, NULL, NULL);
+ if (thumb_uri) {
+ grl_media_set_thumbnail (media, thumb_uri);
+ g_free (thumb_uri);
+ }
+ }
+ }
+
+ g_object_unref (info);
+ }
+
+ grl_media_set_id (media, root_dir ? NULL : path);
+
+ /* URL */
+ str = g_strconcat ("file://", path, NULL);
+ grl_media_set_url (media, str);
+ g_free (str);
+
+ /* Childcount */
+ if (GRL_IS_MEDIA_BOX (media)) {
+ set_container_childcount (path, media, only_fast);
+ }
+
+ g_object_unref (file);
+
+ return media;
+}
+
+static gboolean
+browse_emit_idle (gpointer user_data)
+{
+ BrowseIdleData *idle_data;
+ guint count;
+ GrlFilesystemSource *fs_source;
+
+ GRL_DEBUG ("browse_emit_idle");
+
+ idle_data = (BrowseIdleData *) user_data;
+ fs_source = GRL_FILESYSTEM_SOURCE (idle_data->spec->source);
+
+ if (g_cancellable_is_cancelled (idle_data->cancellable)) {
+ GRL_DEBUG ("Browse operation %d (\"%s\") has been cancelled",
+ idle_data->id, idle_data->path);
+ idle_data->spec->callback(idle_data->spec->source,
+ idle_data->id, NULL, 0,
+ idle_data->spec->user_data, NULL);
+ goto finish;
+ }
+
+ count = 0;
+ do {
+ gchar *entry_path;
+ GrlMedia *content;
+
+ entry_path = (gchar *) idle_data->current->data;
+ content = create_content (NULL,
+ entry_path,
+ idle_data->spec->flags & GRL_RESOLVE_FAST_ONLY,
+ FALSE);
+ g_free (idle_data->current->data);
+
+ idle_data->spec->callback (idle_data->spec->source,
+ idle_data->spec->browse_id,
+ content,
+ idle_data->remaining--,
+ idle_data->spec->user_data,
+ NULL);
+
+ idle_data->current = g_list_next (idle_data->current);
+ count++;
+ } while (count < BROWSE_IDLE_CHUNK_SIZE && idle_data->current);
+
+ if (!idle_data->current)
+ goto finish;
+
+ return TRUE;
+
+finish:
+ g_list_free (idle_data->entries);
+ g_hash_table_remove (fs_source->priv->cancellables,
+ GUINT_TO_POINTER (idle_data->id));
+ g_object_unref (idle_data->cancellable);
+ g_slice_free (BrowseIdleData, idle_data);
+ return FALSE;
+}
+
+static void
+produce_from_path (GrlMediaSourceBrowseSpec *bs, const gchar *path)
+{
+ GDir *dir;
+ GError *error = NULL;
+ const gchar *entry;
+ guint skip, count;
+ GList *entries = NULL;
+ GList *iter;
+
+ /* Open directory */
+ GRL_DEBUG ("Opening directory '%s'", path);
+ dir = g_dir_open (path, 0, &error);
+ if (error) {
+ GRL_WARNING ("Failed to open directory '%s': %s", path, error->message);
+ bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* Filter out media and directories */
+ while ((entry = g_dir_read_name (dir)) != NULL) {
+ gchar *file;
+ if (strcmp (path, G_DIR_SEPARATOR_S)) {
+ file = g_strconcat (path, G_DIR_SEPARATOR_S, entry, NULL);
+ } else {
+ file = g_strconcat (path, entry, NULL);
+ }
+ if (file_is_valid_content (file, FALSE)) {
+ entries = g_list_prepend (entries, file);
+ }
+ }
+
+ /* Apply skip and count */
+ skip = bs->skip;
+ count = bs->count;
+ iter = entries;
+ while (iter) {
+ gboolean remove;
+ GList *tmp;
+ if (skip > 0) {
+ skip--;
+ remove = TRUE;
+ } else if (count > 0) {
+ count--;
+ remove = FALSE;
+ } else {
+ remove = TRUE;
+ }
+ if (remove) {
+ tmp = iter;
+ iter = g_list_next (iter);
+ g_free (tmp->data);
+ entries = g_list_delete_link (entries, tmp);
+ } else {
+ iter = g_list_next (iter);
+ }
+ }
+
+ /* Emit results */
+ if (entries) {
+ /* Use the idle loop to avoid blocking for too long */
+ BrowseIdleData *idle_data = g_slice_new (BrowseIdleData);
+ idle_data->spec = bs;
+ idle_data->remaining = bs->count - count - 1;
+ idle_data->path = path;
+ idle_data->entries = entries;
+ idle_data->current = entries;
+ idle_data->cancellable = g_cancellable_new ();
+ idle_data->id = bs->browse_id;
+ g_hash_table_insert (GRL_FILESYSTEM_SOURCE (bs->source)->priv->cancellables,
+ GUINT_TO_POINTER (bs->browse_id),
+ idle_data->cancellable);
+
+ g_idle_add (browse_emit_idle, idle_data);
+ } else {
+ /* No results */
+ bs->callback (bs->source,
+ bs->browse_id,
+ NULL,
+ 0,
+ bs->user_data,
+ NULL);
+ }
+
+ g_dir_close (dir);
+}
+
+static RecursiveEntry *
+recursive_entry_new (guint depth, GFile *directory)
+{
+ RecursiveEntry *entry;
+
+ entry = g_slice_new(RecursiveEntry);
+ entry->depth = depth;
+ entry->directory = g_object_ref (directory);
+
+ return entry;
+}
+
+static void
+recursive_entry_free (RecursiveEntry *entry)
+{
+ g_object_unref (entry->directory);
+ g_slice_free (RecursiveEntry, entry);
+}
+
+static RecursiveOperation *
+recursive_operation_new ()
+{
+ RecursiveOperation *operation;
+
+ operation = g_slice_new0 (RecursiveOperation);
+ operation->directories = g_queue_new ();
+ operation->cancellable = g_cancellable_new ();
+
+ return operation;
+}
+
+static void
+recursive_operation_free (RecursiveOperation *operation)
+{
+ g_queue_foreach (operation->directories, (GFunc) recursive_entry_free, NULL);
+ g_queue_free (operation->directories);
+ g_object_unref (operation->cancellable);
+ g_slice_free (RecursiveOperation, operation);
+}
+
+static void
+recursive_operation_got_file (GFileEnumerator *enumerator, GAsyncResult *res, RecursiveOperation *operation)
+{
+ GList *files;
+ GError *error = NULL;
+ gboolean continue_operation = TRUE;
+
+ GRL_DEBUG (__func__);
+
+ files = g_file_enumerator_next_files_finish (enumerator, res, &error);
+ if (error) {
+ if (!g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ GRL_WARNING ("Got error: %s", error->message);
+ g_error_free (error);
+ goto finished;
+ }
+
+ if (files) {
+ GFileInfo *file_info;
+ RecursiveEntry *entry;
+
+ /* we assume there is only one GFileInfo in the list since that's what we ask
+ * for when calling g_file_enumerator_next_files_async() */
+ file_info = (GFileInfo *)files->data;
+ g_list_free (files);
+ /* Get the entry we are running now */
+ entry = g_queue_peek_head (operation->directories);
+ switch (g_file_info_get_file_type (file_info)) {
+ case G_FILE_TYPE_SYMBOLIC_LINK:
+ /* we're too afraid of infinite recursion to touch this for now */
+ break;
+ case G_FILE_TYPE_DIRECTORY:
+ {
+ if (entry->depth < operation->max_depth) {
+ GFile *subdir;
+ RecursiveEntry *subentry;
+
+ if (operation->on_dir) {
+ continue_operation = operation->on_dir(file_info, operation);
+ }
+
+ if (continue_operation) {
+ subdir = g_file_get_child (entry->directory,
+ g_file_info_get_name (file_info));
+ subentry = recursive_entry_new (entry->depth + 1, subdir);
+ g_queue_push_tail (operation->directories, subentry);
+ g_object_unref (subdir);
+ } else {
+ g_object_unref (file_info);
+ goto finished;
+ }
+ }
+ }
+ break;
+ case G_FILE_TYPE_REGULAR:
+ if (operation->on_file) {
+ continue_operation = operation->on_file(file_info, operation);
+ if (!continue_operation) {
+ g_object_unref (file_info);
+ goto finished;
+ }
+ }
+ break;
+ default:
+ /* this file is a weirdo, we ignore it */
+ break;
+ }
+ g_object_unref (file_info);
+ } else { /* end of enumerator */
+ goto finished;
+ }
+
+ g_file_enumerator_next_files_async (enumerator, 1, G_PRIORITY_DEFAULT,
+ operation->cancellable,
+ (GAsyncReadyCallback)recursive_operation_got_file,
+ operation);
+
+ return;
+
+finished:
+ /* we're done with this dir/enumerator, let's treat the next one */
+ g_object_unref (enumerator);
+ recursive_entry_free (g_queue_pop_head (operation->directories));
+ if (continue_operation) {
+ recursive_operation_next_entry (operation);
+ } else {
+ recursive_operation_free (operation);
+ }
+}
+
+static void
+recursive_operation_got_entry (GFile *directory, GAsyncResult *res, RecursiveOperation *operation)
+{
+ GError *error = NULL;
+ GFileEnumerator *enumerator;
+
+ GRL_DEBUG (__func__);
+
+ enumerator = g_file_enumerate_children_finish (directory, res, &error);
+ if (error) {
+ GRL_WARNING ("Got error: %s", error->message);
+ g_error_free (error);
+ g_object_unref (enumerator);
+ /* we couldn't get the children of this directory, but we probably have
+ * other directories to try */
+ recursive_entry_free (g_queue_pop_head (operation->directories));
+ recursive_operation_next_entry (operation);
+ return;
+ }
+
+ g_file_enumerator_next_files_async (enumerator, 1, G_PRIORITY_DEFAULT,
+ operation->cancellable,
+ (GAsyncReadyCallback)recursive_operation_got_file,
+ operation);
+}
+
+static void
+recursive_operation_next_entry (RecursiveOperation *operation)
+{
+ RecursiveEntry *entry;
+
+ GRL_DEBUG (__func__);
+
+ if (g_cancellable_is_cancelled (operation->cancellable)) {
+ /* We've been cancelled! */
+ GRL_DEBUG ("Operation has been cancelled");
+ if (operation->on_cancel) {
+ operation->on_cancel(NULL, operation);
+ }
+ goto finished;
+ }
+
+ entry = g_queue_peek_head (operation->directories);
+ if (!entry) { /* We've crawled everything, before reaching count */
+ if (operation->on_finish) {
+ operation->on_finish (NULL, operation);
+ }
+ goto finished;
+ }
+
+ g_file_enumerate_children_async (entry->directory, G_FILE_ATTRIBUTE_STANDARD_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ G_PRIORITY_DEFAULT,
+ operation->cancellable,
+ (GAsyncReadyCallback)recursive_operation_got_entry,
+ operation);
+
+ return;
+
+finished:
+ recursive_operation_free (operation);
+}
+
+static void
+recursive_operation_initialize (RecursiveOperation *operation, GrlFilesystemSource *source)
+{
+ GList *chosen_paths, *path;
+
+ chosen_paths = source->priv->chosen_paths;
+ if (chosen_paths) {
+ for (path = chosen_paths; path; path = g_list_next (path)) {
+ GFile *directory = g_file_new_for_path (path->data);
+ g_queue_push_tail (operation->directories,
+ recursive_entry_new (0, directory));
+ g_object_unref (directory);
+ }
+ } else {
+ const gchar *home;
+ GFile *directory;
+
+ home = g_getenv ("HOME");
+ directory = g_file_new_for_path (home);
+ g_queue_push_tail (operation->directories,
+ recursive_entry_new (0, directory));
+ g_object_unref (directory);
+ }
+}
+
+static gboolean
+cancel_cb (GFileInfo *file_info, RecursiveOperation *operation)
+{
+ GrlFilesystemSource *fs_source;
+
+ if (operation->on_file_data) {
+ GrlMediaSourceSearchSpec *ss =
+ (GrlMediaSourceSearchSpec *) operation->on_file_data;
+ fs_source = GRL_FILESYSTEM_SOURCE (ss->source);
+ g_hash_table_remove (fs_source->priv->cancellables,
+ GUINT_TO_POINTER (ss->search_id));
+ ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, NULL);
+ }
+
+ if (operation->on_dir_data) {
+ /* Remove all monitors */
+ fs_source = GRL_FILESYSTEM_SOURCE (operation->on_dir_data);
+ cancel_monitors (fs_source);
+ }
+ return FALSE;
+}
+
+static gboolean
+finish_cb (GFileInfo *file_info, RecursiveOperation *operation)
+{
+ if (operation->on_file_data) {
+ GrlMediaSourceSearchSpec *ss =
+ (GrlMediaSourceSearchSpec *) operation->on_file_data;
+ g_hash_table_remove (GRL_FILESYSTEM_SOURCE (ss->source)->priv->cancellables,
+ GUINT_TO_POINTER (ss->search_id));
+ ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, NULL);
+ }
+
+ if (operation->on_dir_data) {
+ GRL_FILESYSTEM_SOURCE (operation->on_dir_data)->priv->cancellable_monitors = NULL;
+ }
+
+ return FALSE;
+}
+
+/* return TRUE if more files need to be returned, FALSE if we sent the count */
+static gboolean
+file_cb (GFileInfo *file_info, RecursiveOperation *operation)
+{
+ gchar *needle = NULL;
+ gchar *haystack = NULL;
+ gchar *normalized_needle = NULL;
+ gchar *normalized_haystack = NULL;
+ GrlMediaSourceSearchSpec *ss = operation->on_file_data;
+ gint remaining = -1;
+
+ GRL_DEBUG (__func__);
+
+ if (ss == NULL)
+ return FALSE;
+
+ if (ss->text) {
+ haystack = g_utf8_casefold (g_file_info_get_display_name (file_info), -1);
+ normalized_haystack = g_utf8_normalize (haystack, -1, G_NORMALIZE_ALL);
+
+ needle = g_utf8_casefold (ss->text, -1);
+ normalized_needle = g_utf8_normalize (needle, -1, G_NORMALIZE_ALL);
+ }
+
+ if (!ss->text ||
+ strstr (normalized_haystack, normalized_needle)) {
+ GrlMedia *media = NULL;
+ RecursiveEntry *entry;
+ GFile *file;
+ gchar *path;
+
+ entry = g_queue_peek_head (operation->directories);
+ file = g_file_get_child (entry->directory,
+ g_file_info_get_name (file_info));
+ path = g_file_get_path (file);
+
+ /* FIXME: both file_is_valid_content() and create_content() are likely to block */
+ if (file_is_valid_content (path, FALSE)) {
+ if (ss->skip) {
+ ss->skip--;
+ } else {
+ media = create_content (NULL, path, ss->flags & GRL_RESOLVE_FAST_ONLY, FALSE);
+ }
+ }
+
+ g_free (path);
+ g_object_unref (file);
+
+ if (media) {
+ ss->count--;
+ if (ss->count == 0) {
+ remaining = 0;
+ }
+ ss->callback (ss->source, ss->search_id, media, remaining, ss->user_data, NULL);
+ }
+ }
+
+ g_free (haystack);
+ g_free (normalized_haystack);
+ g_free (needle);
+ g_free (normalized_needle);
+ return remaining == -1;
+}
+
+static void
+notify_parent_change (GrlMediaSource *source, GFile *child, GrlMediaSourceChangeType change)
+{
+ GFile *parent;
+ GrlMedia *media;
+ gchar *parent_path;
+
+ parent = g_file_get_parent (child);
+ if (parent) {
+ parent_path = g_file_get_path (parent);
+ } else {
+ parent_path = g_strdup ("/");
+ }
+
+ media = create_content (NULL, parent_path, GRL_RESOLVE_FAST_ONLY, parent == NULL);
+ grl_media_source_notify_change (source, media, change, FALSE);
+ g_object_unref (media);
+
+ if (parent) {
+ g_object_unref (parent);
+ }
+ g_free (parent_path);
+}
+
+static void
+directory_changed (GFileMonitor *monitor,
+ GFile *file,
+ GFile *other_file,
+ GFileMonitorEvent event,
+ gpointer data)
+{
+ GrlMediaSource *source = GRL_MEDIA_SOURCE (data);
+ gchar *file_path, *other_file_path;
+ gchar *file_parent_path = NULL;
+ gchar *other_file_parent_path = NULL;
+ GFile *file_parent, *other_file_parent;
+ GFileInfo *file_info;
+
+ if (event == G_FILE_MONITOR_EVENT_CHANGES_DONE_HINT ||
+ event == G_FILE_MONITOR_EVENT_CREATED) {
+ file_path = g_file_get_path (file);
+ if (file_is_valid_content (file_path, TRUE)) {
+ notify_parent_change (source,
+ file,
+ (event == G_FILE_MONITOR_EVENT_CREATED)? GRL_CONTENT_ADDED: GRL_CONTENT_CHANGED);
+ if (event == G_FILE_MONITOR_EVENT_CREATED) {
+ file_info = g_file_query_info (file,
+ G_FILE_ATTRIBUTE_STANDARD_TYPE,
+ G_FILE_QUERY_INFO_NOFOLLOW_SYMLINKS,
+ NULL,
+ NULL);
+ if (file_info) {
+ if (g_file_info_get_file_type (file_info) == G_FILE_TYPE_DIRECTORY) {
+ add_monitor (GRL_FILESYSTEM_SOURCE (source), file);
+ }
+ g_object_unref (file_info);
+ }
+ }
+ }
+ g_free (file_path);
+ } else if (event == G_FILE_MONITOR_EVENT_DELETED) {
+ notify_parent_change (source, file, GRL_CONTENT_REMOVED);
+ } else if (event == G_FILE_MONITOR_EVENT_MOVED) {
+ other_file_path = g_file_get_path (other_file);
+ if (file_is_valid_content (other_file_path, TRUE)) {
+ file_parent = g_file_get_parent (file);
+ if (file_parent) {
+ file_parent_path = g_file_get_path (file_parent);
+ g_object_unref (file_parent);
+ } else {
+ file_parent_path = NULL;
+ }
+ other_file_parent = g_file_get_parent (other_file);
+ if (other_file_parent) {
+ other_file_parent_path = g_file_get_path (other_file_parent);
+ g_object_unref (other_file_parent);
+ } else {
+ other_file_parent_path = NULL;
+ }
+
+ if (g_strcmp0 (file_parent_path, other_file_parent_path) == 0) {
+ notify_parent_change (source, file, GRL_CONTENT_CHANGED);
+ } else {
+ notify_parent_change (source, file, GRL_CONTENT_REMOVED);
+ notify_parent_change (source, other_file, GRL_CONTENT_ADDED);
+ }
+ }
+ g_free (file_parent_path);
+ g_free (other_file_parent_path);
+ }
+}
+
+static void
+cancel_monitors (GrlFilesystemSource *fs_source)
+{
+ g_list_foreach (fs_source->priv->monitors,
+ (GFunc) g_file_monitor_cancel,
+ NULL);
+ g_list_foreach (fs_source->priv->monitors,
+ (GFunc) g_object_unref,
+ NULL);
+ g_list_free (fs_source->priv->monitors);
+ fs_source->priv->monitors = NULL;
+}
+
+static void
+add_monitor (GrlFilesystemSource *fs_source, GFile *dir)
+{
+ GFileMonitor *monitor;
+
+ monitor = g_file_monitor_directory (dir, G_FILE_MONITOR_SEND_MOVED, NULL, NULL);
+ if (monitor) {
+ fs_source->priv->monitors = g_list_prepend (fs_source->priv->monitors,
+ monitor);
+ g_signal_connect (monitor,
+ "changed",
+ G_CALLBACK (directory_changed),
+ fs_source);
+ } else {
+ GRL_DEBUG ("Unable to set up monitor in %s\n", g_file_get_path (dir));
+ }
+}
+
+static gboolean
+directory_cb (GFileInfo *dir_info, RecursiveOperation *operation)
+{
+ RecursiveEntry *entry;
+ GFile *dir;
+ GrlFilesystemSource *fs_source;
+
+ fs_source = GRL_FILESYSTEM_SOURCE (operation->on_dir_data);
+ entry = g_queue_peek_head (operation->directories);
+ dir = g_file_get_child (entry->directory,
+ g_file_info_get_name (dir_info));
+
+ add_monitor (fs_source, dir);
+ g_object_unref (dir);
+
+ return TRUE;
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_filesystem_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_MIME,
+ GRL_METADATA_KEY_DATE,
+ GRL_METADATA_KEY_CHILDCOUNT,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_filesystem_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ const gchar *id;
+ GList *chosen_paths;
+
+ GRL_DEBUG ("grl_filesystem_source_browse");
+
+ id = grl_media_get_id (bs->container);
+ chosen_paths = GRL_FILESYSTEM_SOURCE(source)->priv->chosen_paths;
+ if (!id && chosen_paths) {
+ guint remaining = g_list_length (chosen_paths);
+
+ if (remaining == 1) {
+ produce_from_path (bs, chosen_paths->data);
+ } else {
+ for (; chosen_paths; chosen_paths = g_list_next (chosen_paths)) {
+ GrlMedia *content = create_content (NULL,
+ (gchar *) chosen_paths->data,
+ GRL_RESOLVE_FAST_ONLY,
+ FALSE);
+
+ bs->callback (source,
+ bs->browse_id,
+ content,
+ --remaining,
+ bs->user_data,
+ NULL);
+ }
+ }
+ } else {
+ produce_from_path (bs, id ? id : G_DIR_SEPARATOR_S);
+ }
+}
+
+static void grl_filesystem_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss)
+{
+ RecursiveOperation *operation;
+ GrlFilesystemSource *fs_source;
+
+ GRL_DEBUG ("grl_filesystem_source_search");
+
+ fs_source = GRL_FILESYSTEM_SOURCE (source);
+
+ operation = recursive_operation_new ();
+ operation->on_cancel = cancel_cb;
+ operation->on_finish = finish_cb;
+ operation->on_file = file_cb;
+ operation->on_file_data = ss;
+ operation->max_depth = fs_source->priv->max_search_depth;
+ g_hash_table_insert (GRL_FILESYSTEM_SOURCE (source)->priv->cancellables,
+ GUINT_TO_POINTER (ss->search_id),
+ operation->cancellable);
+
+ recursive_operation_initialize (operation, fs_source);
+ recursive_operation_next_entry (operation);
+}
+
+static void
+grl_filesystem_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ const gchar *path;
+ const gchar *id;
+
+ GRL_DEBUG ("grl_filesystem_source_metadata");
+
+ id = grl_media_get_id (ms->media);
+ path = id ? id : G_DIR_SEPARATOR_S;
+
+ if (g_file_test (path, G_FILE_TEST_EXISTS)) {
+ create_content (ms->media, path,
+ ms->flags & GRL_RESOLVE_FAST_ONLY,
+ !id);
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ } else {
+ GError *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "File '%s' does not exist",
+ path);
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ }
+}
+
+static gboolean
+grl_filesystem_test_media_from_uri (GrlMediaSource *source,
+ const gchar *uri)
+{
+ gchar *path, *scheme;
+ GError *error = NULL;
+ gboolean ret = FALSE;
+
+ GRL_DEBUG ("grl_filesystem_test_media_from_uri");
+
+ scheme = g_uri_parse_scheme (uri);
+ ret = (g_strcmp0(scheme, "file") == 0);
+ g_free (scheme);
+ if (!ret)
+ return ret;
+
+ path = g_filename_from_uri (uri, NULL, &error);
+ if (error != NULL) {
+ g_error_free (error);
+ return FALSE;
+ }
+
+ ret = file_is_valid_content (path, TRUE);
+
+ g_free (path);
+ return ret;
+}
+
+static void grl_filesystem_get_media_from_uri (GrlMediaSource *source,
+ GrlMediaSourceMediaFromUriSpec *mfus)
+{
+ gchar *path, *scheme;
+ GError *error = NULL;
+ gboolean ret = FALSE;
+ GrlMedia *media;
+
+ GRL_DEBUG ("grl_filesystem_get_media_from_uri");
+
+ scheme = g_uri_parse_scheme (mfus->uri);
+ ret = (g_strcmp0(scheme, "file") == 0);
+ g_free (scheme);
+ if (!ret) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED,
+ "Cannot create media from '%s'", mfus->uri);
+ mfus->callback (source, NULL, mfus->user_data, error);
+ g_clear_error (&error);
+ return;
+ }
+
+ path = g_filename_from_uri (mfus->uri, NULL, &error);
+ if (error != NULL) {
+ GError *new_error;
+ new_error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED,
+ "Cannot create media from '%s', error message: %s",
+ mfus->uri, error->message);
+ g_clear_error (&error);
+ mfus->callback (source, NULL, mfus->user_data, new_error);
+ g_clear_error (&new_error);
+ goto beach;
+ }
+
+ /* FIXME: this is a blocking call, not sure we want that in here */
+ /* Note: we assume create_content() never returns NULL, which seems to be true */
+ media = create_content (NULL, path, mfus->flags & GRL_RESOLVE_FAST_ONLY,
+ FALSE);
+ mfus->callback (source, media, mfus->user_data, NULL);
+
+beach:
+ g_free (path);
+}
+
+static void
+grl_filesystem_source_cancel (GrlMediaSource *source, guint operation_id)
+{
+ GCancellable *cancellable;
+ GrlFilesystemSourcePrivate *priv;
+
+ priv = GRL_FILESYSTEM_SOURCE (source)->priv;
+
+ cancellable =
+ G_CANCELLABLE (g_hash_table_lookup (priv->cancellables,
+ GUINT_TO_POINTER (operation_id)));
+ if (cancellable)
+ g_cancellable_cancel (cancellable);
+}
+
+static gboolean
+grl_filesystem_source_notify_change_start (GrlMediaSource *source,
+ GError **error)
+{
+ GrlFilesystemSource *fs_source;
+ RecursiveOperation *operation;
+
+ GRL_DEBUG (__func__);
+
+ fs_source = GRL_FILESYSTEM_SOURCE (source);
+ operation = recursive_operation_new ();
+ operation->on_cancel = cancel_cb;
+ operation->on_finish = finish_cb;
+ operation->on_dir = directory_cb;
+ operation->on_dir_data = fs_source;
+ operation->max_depth = fs_source->priv->max_search_depth;
+
+ fs_source->priv->cancellable_monitors = operation->cancellable;
+
+ recursive_operation_initialize (operation, fs_source);
+ recursive_operation_next_entry (operation);
+
+ return TRUE;
+}
+
+static gboolean
+grl_filesystem_source_notify_change_stop (GrlMediaSource *source,
+ GError **error)
+{
+ GrlFilesystemSource *fs_source = GRL_FILESYSTEM_SOURCE (source);
+
+ /* Check if notifying is being initialized */
+ if (fs_source->priv->cancellable_monitors) {
+ g_cancellable_cancel (fs_source->priv->cancellable_monitors);
+ fs_source->priv->cancellable_monitors = NULL;
+ } else {
+ /* Cancel and remove all monitors */
+ cancel_monitors (fs_source);
+ }
+
+ return TRUE;
+}
diff --git a/src/media/filesystem/grl-filesystem.h b/src/media/filesystem/grl-filesystem.h
new file mode 100644
index 0000000..428c61b
--- /dev/null
+++ b/src/media/filesystem/grl-filesystem.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010, 2011 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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_FILESYSTEM_SOURCE_H_
+#define _GRL_FILESYSTEM_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_FILESYSTEM_SOURCE_TYPE \
+ (grl_filesystem_source_get_type ())
+
+#define GRL_FILESYSTEM_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_FILESYSTEM_SOURCE_TYPE, \
+ GrlFilesystemSource))
+
+#define GRL_IS_FILESYSTEM_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_FILESYSTEM_SOURCE_TYPE))
+
+#define GRL_FILESYSTEM_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_FILESYSTEM_SOURCE_TYPE, \
+ GrlFilesystemSourceClass))
+
+#define GRL_IS_FILESYSTEM_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass) \
+ GRL_FILESYSTEM_SOURCE_TYPE))
+
+#define GRL_FILESYSTEM_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_FILESYSTEM_SOURCE_TYPE, \
+ GrlFilesystemSourceClass))
+
+/* --- Grilo Configuration --- */
+#define GRILO_CONF_CHOSEN_PATH "base-path"
+#define GRILO_CONF_MAX_SEARCH_DEPTH "maximum-search-depth"
+#define GRILO_CONF_MAX_SEARCH_DEPTH_DEFAULT 6
+
+
+typedef struct _GrlFilesystemSource GrlFilesystemSource;
+typedef struct _GrlFilesystemSourcePrivate GrlFilesystemSourcePrivate;
+
+struct _GrlFilesystemSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlFilesystemSourcePrivate *priv;
+};
+
+typedef struct _GrlFilesystemSourceClass GrlFilesystemSourceClass;
+
+struct _GrlFilesystemSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_filesystem_source_get_type (void);
+
+#endif /* _GRL_FILESYSTEM_SOURCE_H_ */
diff --git a/src/media/filesystem/grl-filesystem.xml b/src/media/filesystem/grl-filesystem.xml
new file mode 100644
index 0000000..cfb9bc6
--- /dev/null
+++ b/src/media/filesystem/grl-filesystem.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Filesystem</name>
+ <description>A plugin for browsing the filesystem</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/flickr/Makefile.am b/src/media/flickr/Makefile.am
new file mode 100644
index 0000000..66da488
--- /dev/null
+++ b/src/media/flickr/Makefile.am
@@ -0,0 +1,40 @@
+#
+# Makefile.am
+#
+# Author: Juan A. Suarez Romero <jasuarez igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+libplugin_LTLIBRARIES = libgrlflickr.la
+
+libgrlflickr_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(XML_CFLAGS) \
+ $(GRLNET_CFLAGS)
+
+libgrlflickr_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(XML_LIBS) \
+ $(GRLNET_LIBS)
+
+libgrlflickr_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlflickr_la_SOURCES = \
+ grl-flickr.c \
+ grl-flickr.h \
+ gflickr.c \
+ gflickr.h
+
+libplugindir = $(GRL_PLUGINS_DIR)
+flickrlibxmldir = $(GRL_PLUGINS_CONF_DIR)
+flickrlibxml_DATA = $(FLICKR_PLUGIN_ID).xml
+
+EXTRA_DIST = $(flickrlibxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/flickr/gflickr.c b/src/media/flickr/gflickr.c
new file mode 100644
index 0000000..695c7dc
--- /dev/null
+++ b/src/media/flickr/gflickr.c
@@ -0,0 +1,1196 @@
+#include "gflickr.h"
+#include "grl-flickr.h" /* log domain */
+
+#include <libxml/xpath.h>
+#include <gio/gio.h>
+#include <string.h>
+
+#include <grilo.h>
+#include <net/grl-net.h>
+
+
+#define G_FLICKR_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ G_FLICKR_TYPE, \
+ GFlickrPrivate))
+
+#define GRL_LOG_DOMAIN_DEFAULT flickr_log_domain
+
+#define FLICKR_PHOTO_ORIG_URL \
+ "http://farm%s.static.flickr.com/%s/%s_%s_o.%s"
+
+#define FLICKR_PHOTO_THUMB_URL \
+ "http://farm%s.static.flickr.com/%s/%s_%s_t.jpg"
+
+#define FLICKR_PHOTO_LARGEST_URL \
+ "http://farm%s.static.flickr.com/%s/%s_%s_b.jpg"
+
+#define FLICKR_ENDPOINT "http://api.flickr.com/services/rest/?"
+#define FLICKR_AUTHPOINT "http://flickr.com/services/auth/?"
+
+#define FLICKR_PHOTOS_SEARCH_METHOD "flickr.photos.search"
+#define FLICKR_PHOTOS_GETINFO_METHOD "flickr.photos.getInfo"
+#define FLICKR_PHOTOS_GETRECENT_METHOD "flickr.photos.getRecent"
+#define FLICKR_PHOTOSETS_GETLIST_METHOD "flickr.photosets.getList"
+#define FLICKR_PHOTOSETS_GETPHOTOS_METHOD "flickr.photosets.getPhotos"
+#define FLICKR_TAGS_GETHOTLIST_METHOD "flickr.tags.getHotList"
+#define FLICKR_AUTH_GETFROB_METHOD "flickr.auth.getFrob"
+#define FLICKR_AUTH_GETTOKEN_METHOD "flickr.auth.getToken"
+#define FLICKR_AUTH_CHECKTOKEN_METHOD "flickr.auth.checkToken"
+
+#define FLICKR_PHOTOS_SEARCH \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_PHOTOS_SEARCH_METHOD \
+ "&user_id=%s" \
+ "&extras=media,date_taken,owner_name,url_o,url_t" \
+ "&per_page=%d" \
+ "&page=%d" \
+ "&tags=%s" \
+ "&text=%s" \
+ "%s"
+
+#define FLICKR_PHOTOS_GETRECENT \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_PHOTOS_GETRECENT_METHOD \
+ "&extras=media,date_taken,owner_name,url_o,url_t" \
+ "&per_page=%d" \
+ "&page=%d" \
+ "%s"
+
+#define FLICKR_PHOTOSETS_GETLIST \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_PHOTOSETS_GETLIST_METHOD \
+ "%s" \
+ "%s"
+
+#define FLICKR_PHOTOSETS_GETPHOTOS \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_PHOTOSETS_GETPHOTOS_METHOD \
+ "&photoset_id=%s" \
+ "&extras=media,date_taken,owner_name,url_o,url_t" \
+ "&per_page=%d" \
+ "&page=%d" \
+ "%s"
+
+#define FLICKR_TAGS_GETHOTLIST \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_TAGS_GETHOTLIST_METHOD \
+ "&count=%d" \
+ "%s"
+
+#define FLICKR_PHOTOS_GETINFO \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_PHOTOS_GETINFO_METHOD \
+ "&photo_id=%ld" \
+ "%s"
+
+#define FLICKR_AUTH_GETFROB \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_AUTH_GETFROB_METHOD
+
+#define FLICKR_AUTH_GETTOKEN \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_AUTH_GETTOKEN_METHOD \
+ "&frob=%s"
+
+#define FLICKR_AUTH_CHECKTOKEN \
+ FLICKR_ENDPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&method=" FLICKR_AUTH_CHECKTOKEN_METHOD \
+ "&auth_token=%s"
+
+#define FLICKR_AUTH_LOGINLINK \
+ FLICKR_AUTHPOINT \
+ "api_key=%s" \
+ "&api_sig=%s" \
+ "&frob=%s" \
+ "&perms=%s"
+
+typedef void (*ParseXML) (const gchar *xml_result, gpointer user_data);
+
+typedef struct {
+ GFlickr *flickr;
+ ParseXML parse_xml;
+ GFlickrHashTableCb hashtable_cb;
+ GFlickrListCb list_cb;
+ gpointer user_data;
+} GFlickrData;
+
+struct _GFlickrPrivate {
+ gchar *api_key;
+ gchar *auth_secret;
+ gchar *auth_token;
+ gint per_page;
+
+ GrlNetWc *wc;
+};
+
+static void g_flickr_finalize (GObject *object);
+
+/* -------------------- GOBJECT -------------------- */
+
+G_DEFINE_TYPE (GFlickr, g_flickr, G_TYPE_OBJECT);
+
+static void
+g_flickr_class_init (GFlickrClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = g_flickr_finalize;
+
+ g_type_class_add_private (klass, sizeof (GFlickrPrivate));
+}
+
+static void
+g_flickr_init (GFlickr *f)
+{
+ f->priv = G_FLICKR_GET_PRIVATE (f);
+ f->priv->per_page = 100;
+}
+
+static void
+g_flickr_finalize (GObject *object)
+{
+ GFlickr *f = G_FLICKR (object);
+ g_free (f->priv->api_key);
+ g_free (f->priv->auth_token);
+ g_free (f->priv->auth_secret);
+
+ if (f->priv->wc)
+ g_object_unref (f->priv->wc);
+
+ G_OBJECT_CLASS (g_flickr_parent_class)->finalize (object);
+}
+
+GFlickr *
+g_flickr_new (const gchar *api_key, const gchar *auth_secret, const gchar *auth_token)
+{
+ g_return_val_if_fail (api_key && auth_secret, NULL);
+
+ GFlickr *f = g_object_new (G_FLICKR_TYPE, NULL);
+ f->priv->api_key = g_strdup (api_key);
+ f->priv->auth_secret = g_strdup (auth_secret);
+ f->priv->auth_token = g_strdup (auth_token);
+
+ return f;
+}
+
+/* -------------------- PRIVATE API -------------------- */
+
+static gchar *
+get_api_sig (const gchar *secret, ...)
+{
+ GHashTable *hash;
+ GList *key_iter;
+ GList *keys;
+ GString *to_sign;
+ gchar *api_sig;
+ gchar *key;
+ gchar *value;
+ gint text_size = strlen (secret);
+ va_list va_params;
+
+ hash = g_hash_table_new (g_str_hash, g_str_equal);
+
+ va_start (va_params, secret);
+ while ((key = va_arg (va_params, gchar *))) {
+ text_size += strlen (key);
+ value = va_arg (va_params, gchar *);
+ text_size += strlen (value);
+ g_hash_table_insert (hash, key, value);
+ }
+ va_end (va_params);
+
+ to_sign = g_string_sized_new (text_size);
+ g_string_append (to_sign, secret);
+
+ keys = g_hash_table_get_keys (hash);
+ keys = g_list_sort (keys, (GCompareFunc) g_strcmp0);
+ for (key_iter = keys; key_iter; key_iter = g_list_next (key_iter)) {
+ g_string_append (to_sign, key_iter->data);
+ g_string_append (to_sign, g_hash_table_lookup (hash, key_iter->data));
+ }
+
+ api_sig = g_compute_checksum_for_string (G_CHECKSUM_MD5, to_sign->str, -1);
+ g_hash_table_unref (hash);
+ g_list_free (keys);
+ g_string_free (to_sign, TRUE);
+
+ return api_sig;
+}
+
+static gchar *
+get_xpath_element (const gchar *content,
+ const gchar *xpath_element)
+{
+ gchar *element = NULL;
+ xmlDocPtr xmldoc = NULL;
+ xmlXPathContextPtr xpath_ctx = NULL;
+ xmlXPathObjectPtr xpath_res = NULL;
+
+ xmldoc = xmlReadMemory (content, xmlStrlen ((xmlChar *) content), NULL, NULL,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ if (xmldoc) {
+ xpath_ctx = xmlXPathNewContext (xmldoc);
+ if (xpath_ctx) {
+ xpath_res = xmlXPathEvalExpression ((xmlChar *) xpath_element, xpath_ctx);
+ if (xpath_res && xpath_res->nodesetval->nodeTab) {
+ element =
+ (gchar *) xmlNodeListGetString (xmldoc,
+ xpath_res->nodesetval->nodeTab[0]->xmlChildrenNode,
+ 1);
+ }
+ }
+ }
+
+ /* Free data */
+ if (xmldoc) {
+ xmlFreeDoc (xmldoc);
+ }
+
+ if (xpath_ctx) {
+ xmlXPathFreeContext (xpath_ctx);
+ }
+
+ if (xpath_res) {
+ xmlXPathFreeObject (xpath_res);
+ }
+
+ return element;
+}
+
+static gboolean
+result_is_correct (xmlNodePtr node)
+{
+ gboolean correct = FALSE;
+ xmlChar *stat;
+
+ if (xmlStrcmp (node->name, (const xmlChar *) "rsp") == 0) {
+ stat = xmlGetProp (node, (const xmlChar *) "stat");
+ if (stat && xmlStrcmp (stat, (const xmlChar *) "ok") == 0) {
+ correct = TRUE;
+ xmlFree (stat);
+ }
+ }
+
+ return correct;
+}
+
+static void
+add_node (xmlNodePtr node, GHashTable *photo)
+{
+ xmlAttrPtr attr;
+
+ for (attr = node->properties; attr != NULL; attr = attr->next) {
+ g_hash_table_insert (photo,
+ g_strconcat ((const gchar *) node->name,
+ "_",
+ (const gchar *) attr->name,
+ NULL),
+ (gchar *) xmlGetProp (node, attr->name));
+ }
+}
+
+static GHashTable *
+get_photo (xmlNodePtr node)
+{
+ GHashTable *photo = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+
+ /* Add photo node */
+ add_node (node, photo);
+
+ /* Add children nodes with their properties */
+
+ node = node->xmlChildrenNode;
+
+ while (node) {
+ if (xmlStrcmp (node->name, (const xmlChar *) "owner") == 0 ||
+ xmlStrcmp (node->name, (const xmlChar *) "dates") == 0) {
+ add_node (node, photo);
+ } else if (xmlStrcmp (node->name, (const xmlChar *) "title") == 0 ||
+ xmlStrcmp (node->name, (const xmlChar *) "description") == 0) {
+ g_hash_table_insert (photo,
+ g_strdup ((const gchar *) node->name),
+ (gchar *) xmlNodeGetContent (node));
+ }
+
+ node = node->next;
+ }
+
+ return photo;
+}
+
+static GHashTable *
+get_photoset (xmlNodePtr node)
+{
+ GHashTable *photoset = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+
+ /* Add photoset node */
+ add_node (node, photoset);
+
+ /* Add children nodes with their properties */
+ node = node->xmlChildrenNode;
+
+ while (node) {
+ g_hash_table_insert (photoset,
+ g_strdup ((const gchar *) node->name),
+ (gchar *) xmlNodeGetContent (node));
+ node = node->next;
+ }
+
+ return photoset;
+}
+
+static gchar *
+get_tag (xmlNodePtr node)
+{
+ if (xmlStrcmp (node->name, (const xmlChar *) "tag") == 0) {
+ return (gchar *) xmlNodeGetContent (node);
+ } else {
+ return NULL;
+ }
+}
+
+static GHashTable *
+get_token_info (xmlNodePtr node)
+{
+ GHashTable *token = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+ node = node->xmlChildrenNode;
+
+ while (node) {
+ g_hash_table_insert (token,
+ g_strdup ((const gchar *) node->name),
+ (gchar *) xmlNodeGetContent (node));
+ add_node (node, token);
+ node = node->next;
+ }
+
+ return token;
+}
+
+static void
+process_photo_result (const gchar *xml_result, gpointer user_data)
+{
+ xmlDocPtr doc;
+ xmlNodePtr node;
+ GFlickrData *data = (GFlickrData *) user_data;
+ GHashTable *photo;
+
+ doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
+ NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ node = xmlDocGetRootElement (doc);
+
+ /* Check result is ok */
+ if (!node || !result_is_correct (node)) {
+ data->hashtable_cb (data->flickr, NULL, data->user_data);
+ } else {
+ node = node->xmlChildrenNode;
+
+ photo = get_photo (node);
+ data->hashtable_cb (data->flickr, photo, data->user_data);
+ g_hash_table_unref (photo);
+ }
+ g_object_unref (data->flickr);
+ g_slice_free (GFlickrData, data);
+ xmlFreeDoc (doc);
+}
+
+static void
+process_photolist_result (const gchar *xml_result, gpointer user_data)
+{
+ GFlickrData *data = (GFlickrData *) user_data;
+ GList *photolist = NULL;
+ xmlDocPtr doc;
+ xmlNodePtr node;
+
+ doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
+ NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ node = xmlDocGetRootElement (doc);
+
+ /* Check result is ok */
+ if (!node || !result_is_correct (node)) {
+ data->list_cb (data->flickr, NULL, data->user_data);
+ } else {
+ node = node->xmlChildrenNode;
+
+ /* Now we're at "photo pages" node */
+ node = node->xmlChildrenNode;
+ while (node) {
+ photolist = g_list_prepend (photolist, get_photo (node));
+ node = node->next;
+ }
+
+ data->list_cb (data->flickr, g_list_reverse (photolist), data->user_data);
+ g_list_foreach (photolist, (GFunc) g_hash_table_unref, NULL);
+ g_list_free (photolist);
+ }
+ g_object_unref (data->flickr);
+ g_slice_free (GFlickrData, data);
+ xmlFreeDoc (doc);
+}
+
+static void
+process_taglist_result (const gchar *xml_result, gpointer user_data)
+{
+ GFlickrData *data = (GFlickrData *) user_data;
+ GList *taglist = NULL;
+ xmlDocPtr doc;
+ xmlNodePtr node;
+
+ doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
+ NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ node = xmlDocGetRootElement (doc);
+
+ /* Check if result is OK */
+ if (!node || !result_is_correct (node)) {
+ data->list_cb (data->flickr, NULL, data->user_data);
+ } else {
+ node = node->xmlChildrenNode;
+
+ /* Now we're at "hot tags" node */
+ node = node->xmlChildrenNode;
+ while (node) {
+ taglist = g_list_prepend (taglist, get_tag (node));
+ node = node->next;
+ }
+
+ data->list_cb (data->flickr, g_list_reverse (taglist), data->user_data);
+ g_list_foreach (taglist, (GFunc) g_free, NULL);
+ g_list_free (taglist);
+ }
+ g_object_unref (data->flickr);
+ g_slice_free (GFlickrData, data);
+ xmlFreeDoc (doc);
+}
+
+static void
+process_photosetslist_result (const gchar *xml_result, gpointer user_data)
+{
+ GFlickrData *data = (GFlickrData *) user_data;
+ GList *photosets = NULL;
+ xmlDocPtr doc;
+ xmlNodePtr node;
+
+ doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
+ NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ node = xmlDocGetRootElement (doc);
+
+ /* Check if result is OK */
+ if (!node || !result_is_correct (node)) {
+ data->list_cb (data->flickr, NULL, data->user_data);
+ } else {
+ node = node->xmlChildrenNode;
+
+ /* Now we're at "photosets" node */
+ node = node->xmlChildrenNode;
+ while (node) {
+ photosets = g_list_prepend (photosets, get_photoset (node));
+ node = node->next;
+ }
+
+ data->list_cb (data->flickr, g_list_reverse (photosets), data->user_data);
+ g_list_foreach (photosets, (GFunc) g_hash_table_unref, NULL);
+ g_list_free (photosets);
+ }
+ g_object_unref (data->flickr);
+ g_slice_free (GFlickrData, data);
+ xmlFreeDoc (doc);
+}
+
+static void
+process_photosetsphotos_result (const gchar *xml_result, gpointer user_data)
+{
+ GFlickrData *data = (GFlickrData *) user_data;
+ GList *list = NULL;
+ xmlDocPtr doc;
+ xmlNodePtr node;
+
+ doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
+ NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ node = xmlDocGetRootElement (doc);
+
+ /* Check result is ok */
+ if (!node || !result_is_correct (node)) {
+ data->list_cb (data->flickr, NULL, data->user_data);
+ } else {
+ node = node->xmlChildrenNode;
+
+ /* Now we're at "photoset page" node */
+ node = node->xmlChildrenNode;
+ while (node) {
+ list = g_list_prepend (list, get_photo (node));
+ node = node->next;
+ }
+
+ data->list_cb (data->flickr, g_list_reverse (list), data->user_data);
+ g_list_foreach (list, (GFunc) g_hash_table_unref, NULL);
+ g_list_free (list);
+ }
+ g_object_unref (data->flickr);
+ g_slice_free (GFlickrData, data);
+ xmlFreeDoc (doc);
+}
+
+static void
+process_token_result (const gchar *xml_result, gpointer user_data)
+{
+ xmlDocPtr doc;
+ xmlNodePtr node;
+ GFlickrData *data = (GFlickrData *) user_data;
+ GHashTable *token;
+
+ doc = xmlReadMemory (xml_result, xmlStrlen ((xmlChar*) xml_result), NULL,
+ NULL, XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ node = xmlDocGetRootElement (doc);
+
+ /* Check if result is OK */
+ if (!node || !result_is_correct (node)) {
+ data->hashtable_cb (data->flickr, NULL, data->user_data);
+ } else {
+ node = node->xmlChildrenNode;
+ token = get_token_info (node);
+ data->hashtable_cb (data->flickr, token, data->user_data);
+ g_hash_table_unref (token);
+ }
+
+ g_object_unref (data->flickr);
+ g_slice_free (GFlickrData, data);
+ xmlFreeDoc (doc);
+}
+
+inline static GrlNetWc *
+get_wc (GFlickr *f)
+{
+ if (!f->priv->wc)
+ f->priv->wc = grl_net_wc_new ();
+
+ return f->priv->wc;
+}
+
+static void
+read_done_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ gchar *content = NULL;
+ GError *wc_error = NULL;
+ GFlickrData *data = (GFlickrData *) user_data;
+
+ grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res,
+ &content,
+ NULL,
+ &wc_error);
+
+ data->parse_xml (content, user_data);
+}
+
+static void
+read_url_async (GFlickr *f,
+ const gchar *url,
+ gpointer user_data)
+{
+ GRL_DEBUG ("Opening '%s'", url);
+ grl_net_wc_request_async (get_wc (f),
+ url,
+ NULL,
+ read_done_cb,
+ user_data);
+}
+
+/* -------------------- PUBLIC API -------------------- */
+
+void
+g_flickr_set_per_page (GFlickr *f, gint per_page)
+{
+ g_return_if_fail (G_IS_FLICKR (f));
+
+ f->priv->per_page = per_page;
+}
+
+void
+g_flickr_photos_getInfo (GFlickr *f,
+ glong photo_id,
+ GFlickrHashTableCb callback,
+ gpointer user_data)
+{
+ gchar *auth;
+
+ g_return_if_fail (G_IS_FLICKR (f));
+
+ gchar *str_photo_id = g_strdup_printf ("%ld", photo_id);
+ gchar *api_sig = get_api_sig (f->priv->auth_secret,
+ "api_key", f->priv->api_key,
+ "method", FLICKR_PHOTOS_GETINFO_METHOD,
+ "photo_id", str_photo_id,
+ f->priv->auth_token? "auth_token": "",
+ f->priv->auth_token? f->priv->auth_token: "",
+ NULL);
+ g_free (str_photo_id);
+
+ /* Build the request */
+ if (f->priv->auth_token) {
+ auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
+ } else {
+ auth = g_strdup ("");
+ }
+
+ gchar *request = g_strdup_printf (FLICKR_PHOTOS_GETINFO,
+ f->priv->api_key,
+ api_sig,
+ photo_id,
+ auth);
+ g_free (api_sig);
+ g_free (auth);
+
+ GFlickrData *gfd = g_slice_new (GFlickrData);
+ gfd->flickr = g_object_ref (f);
+ gfd->parse_xml = process_photo_result;
+ gfd->hashtable_cb = callback;
+ gfd->user_data = user_data;
+
+ read_url_async (f, request, gfd);
+ g_free (request);
+}
+
+void
+g_flickr_photos_search (GFlickr *f,
+ const gchar *user_id,
+ const gchar *text,
+ const gchar *tags,
+ gint page,
+ GFlickrListCb callback,
+ gpointer user_data)
+{
+ gchar *auth;
+ g_return_if_fail (G_IS_FLICKR (f));
+
+ if (!user_id) {
+ user_id = "";
+ }
+
+ if (!text) {
+ text = "";
+ }
+
+ if (!tags) {
+ tags = "";
+ }
+
+ gchar *strpage = g_strdup_printf ("%d", page);
+ gchar *strperpage = g_strdup_printf ("%d", f->priv->per_page);
+
+ gchar *api_sig =
+ get_api_sig (f->priv->auth_secret,
+ "api_key", f->priv->api_key,
+ "extras", "media,date_taken,owner_name,url_o,url_t",
+ "method", FLICKR_PHOTOS_SEARCH_METHOD,
+ "user_id", user_id,
+ "page", strpage,
+ "per_page", strperpage,
+ "tags", tags,
+ "text", text,
+ f->priv->auth_token? "auth_token": "",
+ f->priv->auth_token? f->priv->auth_token: "",
+ NULL);
+ g_free (strpage);
+ g_free (strperpage);
+
+ /* Build the request */
+ if (f->priv->auth_token) {
+ auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
+ } else {
+ auth = g_strdup ("");
+ }
+
+ gchar *request = g_strdup_printf (FLICKR_PHOTOS_SEARCH,
+ f->priv->api_key,
+ api_sig,
+ user_id,
+ f->priv->per_page,
+ page,
+ tags,
+ text,
+ auth);
+ g_free (api_sig);
+ g_free (auth);
+
+ GFlickrData *gfd = g_slice_new (GFlickrData);
+ gfd->flickr = g_object_ref (f);
+ gfd->parse_xml = process_photolist_result;
+ gfd->list_cb = callback;
+ gfd->user_data = user_data;
+
+ read_url_async (f, request, gfd);
+ g_free (request);
+}
+
+void
+g_flickr_photos_getRecent (GFlickr *f,
+ gint page,
+ GFlickrListCb callback,
+ gpointer user_data)
+{
+ gchar *auth;
+ g_return_if_fail (G_IS_FLICKR (f));
+
+ gchar *strpage = g_strdup_printf ("%d", page);
+ gchar *strperpage = g_strdup_printf ("%d", f->priv->per_page);
+
+ gchar *api_sig =
+ get_api_sig (f->priv->auth_secret,
+ "api_key", f->priv->api_key,
+ "extras", "media,date_taken,owner_name,url_o,url_t",
+ "method", FLICKR_PHOTOS_GETRECENT_METHOD,
+ "page", strpage,
+ "per_page", strperpage,
+ f->priv->auth_token? "auth_token": "",
+ f->priv->auth_token? f->priv->auth_token: "",
+ NULL);
+ g_free (strpage);
+ g_free (strperpage);
+
+ /* Build the request */
+ if (f->priv->auth_token) {
+ auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
+ } else {
+ auth = g_strdup ("");
+ }
+
+ gchar *request = g_strdup_printf (FLICKR_PHOTOS_GETRECENT,
+ f->priv->api_key,
+ api_sig,
+ f->priv->per_page,
+ page,
+ auth);
+ g_free (api_sig);
+ g_free (auth);
+
+ GFlickrData *gfd = g_slice_new (GFlickrData);
+ gfd->flickr = g_object_ref (f);
+ gfd->parse_xml = process_photolist_result;
+ gfd->list_cb = callback;
+ gfd->user_data = user_data;
+
+ read_url_async (f, request, gfd);
+ g_free (request);
+}
+
+gchar *
+g_flickr_photo_url_original (GFlickr *f, GHashTable *photo)
+{
+ gchar *extension;
+ gchar *farm_id;
+ gchar *o_secret;
+ gchar *photo_id;
+ gchar *server_id;
+
+ if (!photo) {
+ return NULL;
+ }
+
+ extension = g_hash_table_lookup (photo, "photo_originalformat");
+ farm_id = g_hash_table_lookup (photo, "photo_farm");
+ o_secret = g_hash_table_lookup (photo, "photo_originalsecret");
+ photo_id = g_hash_table_lookup (photo, "photo_id");
+ server_id = g_hash_table_lookup (photo, "photo_server");
+
+ if (!extension || !farm_id || !o_secret || !photo_id || !server_id) {
+ return NULL;
+ } else {
+ return g_strdup_printf (FLICKR_PHOTO_ORIG_URL,
+ farm_id,
+ server_id,
+ photo_id,
+ o_secret,
+ extension);
+ }
+}
+
+gchar *
+g_flickr_photo_url_thumbnail (GFlickr *f, GHashTable *photo)
+{
+ gchar *farm_id;
+ gchar *secret;
+ gchar *photo_id;
+ gchar *server_id;
+
+ if (!photo) {
+ return NULL;
+ }
+
+ farm_id = g_hash_table_lookup (photo, "photo_farm");
+ secret = g_hash_table_lookup (photo, "photo_secret");
+ photo_id = g_hash_table_lookup (photo, "photo_id");
+ server_id = g_hash_table_lookup (photo, "photo_server");
+
+ if (!farm_id || !secret || !photo_id || !server_id) {
+ return NULL;
+ } else {
+ return g_strdup_printf (FLICKR_PHOTO_THUMB_URL,
+ farm_id,
+ server_id,
+ photo_id,
+ secret);
+ }
+}
+
+gchar *
+g_flickr_photo_url_largest (GFlickr *f, GHashTable *photo)
+{
+ gchar *farm_id;
+ gchar *secret;
+ gchar *photo_id;
+ gchar *server_id;
+
+ if (!photo) {
+ return NULL;
+ }
+
+ farm_id = g_hash_table_lookup (photo, "photo_farm");
+ secret = g_hash_table_lookup (photo, "photo_secret");
+ photo_id = g_hash_table_lookup (photo, "photo_id");
+ server_id = g_hash_table_lookup (photo, "photo_server");
+
+ if (!farm_id || !secret || !photo_id || !server_id) {
+ return NULL;
+ } else {
+ return g_strdup_printf (FLICKR_PHOTO_LARGEST_URL,
+ farm_id,
+ server_id,
+ photo_id,
+ secret);
+ }
+}
+
+void
+g_flickr_tags_getHotList (GFlickr *f,
+ gint count,
+ GFlickrListCb callback,
+ gpointer user_data)
+{
+ gchar *auth;
+
+ g_return_if_fail (G_IS_FLICKR (f));
+
+ gchar *strcount = g_strdup_printf ("%d", count);
+
+ gchar *api_sig = get_api_sig (f->priv->auth_secret,
+ "api_key", f->priv->api_key,
+ "count", strcount,
+ "method", FLICKR_TAGS_GETHOTLIST_METHOD,
+ f->priv->auth_token? "auth_token": "",
+ f->priv->auth_token? f->priv->auth_token: "",
+ NULL);
+ g_free (strcount);
+
+ /* Build the request */
+ if (f->priv->auth_token) {
+ auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
+ } else {
+ auth = g_strdup ("");
+ }
+ gchar *request = g_strdup_printf (FLICKR_TAGS_GETHOTLIST,
+ f->priv->api_key,
+ api_sig,
+ count,
+ auth);
+ g_free (api_sig);
+ g_free (auth);
+
+ GFlickrData *gfd = g_slice_new (GFlickrData);
+ gfd->flickr = g_object_ref (f);
+ gfd->parse_xml = process_taglist_result;
+ gfd->list_cb = callback;
+ gfd->user_data = user_data;
+
+ read_url_async (f, request, gfd);
+ g_free (request);
+}
+
+void
+g_flickr_photosets_getList (GFlickr *f,
+ const gchar *user_id,
+ GFlickrListCb callback,
+ gpointer user_data)
+{
+ gchar *user;
+ gchar *auth;
+
+ gchar *api_sig = get_api_sig (f->priv->auth_secret,
+ "api_key", f->priv->api_key,
+ "method", FLICKR_PHOTOSETS_GETLIST_METHOD,
+ user_id? "user_id": "",
+ user_id? user_id: "",
+ f->priv->auth_token? "auth_token": "",
+ f->priv->auth_token? f->priv->auth_token: "",
+ NULL);
+
+ /* Build the request */
+ if (user_id) {
+ user = g_strdup_printf ("&user_id=%s", user_id);
+ } else {
+ user = g_strdup ("");
+ }
+
+ if (f->priv->auth_token) {
+ auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
+ } else {
+ auth = g_strdup ("");
+ }
+
+ gchar *request = g_strdup_printf (FLICKR_PHOTOSETS_GETLIST,
+ f->priv->api_key,
+ api_sig,
+ user,
+ auth);
+
+ g_free (api_sig);
+ g_free (user);
+ g_free (auth);
+
+ GFlickrData *gfd = g_slice_new (GFlickrData);
+ gfd->flickr = g_object_ref (f);
+ gfd->parse_xml = process_photosetslist_result;
+ gfd->list_cb = callback;
+ gfd->user_data = user_data;
+
+ read_url_async (f, request, gfd);
+ g_free (request);
+}
+
+void
+g_flickr_photosets_getPhotos (GFlickr *f,
+ const gchar *photoset_id,
+ gint page,
+ GFlickrListCb callback,
+ gpointer user_data)
+{
+ gchar *auth;
+
+ g_return_if_fail (G_IS_FLICKR (f));
+ g_return_if_fail (photoset_id);
+
+ gchar *strpage = g_strdup_printf ("%d", page);
+ gchar *strperpage = g_strdup_printf ("%d", f->priv->per_page);
+
+ gchar *api_sig =
+ get_api_sig (f->priv->auth_secret,
+ "api_key", f->priv->api_key,
+ "photoset_id", photoset_id,
+ "extras", "media,date_taken,owner_name,url_o,url_t",
+ "method", FLICKR_PHOTOSETS_GETPHOTOS_METHOD,
+ "page", strpage,
+ "per_page", strperpage,
+ f->priv->auth_token? "auth_token": "",
+ f->priv->auth_token? f->priv->auth_token: "",
+ NULL);
+
+ g_free (strpage);
+ g_free (strperpage);
+
+ /* Build the request */
+ if (f->priv->auth_token) {
+ auth = g_strdup_printf ("&auth_token=%s", f->priv->auth_token);
+ } else {
+ auth = g_strdup ("");
+ }
+
+ gchar *request = g_strdup_printf (FLICKR_PHOTOSETS_GETPHOTOS,
+ f->priv->api_key,
+ api_sig,
+ photoset_id,
+ f->priv->per_page,
+ page,
+ auth);
+ g_free (api_sig);
+ g_free (auth);
+
+ GFlickrData *gfd = g_slice_new (GFlickrData);
+ gfd->flickr = g_object_ref (f);
+ gfd->parse_xml = process_photosetsphotos_result;
+ gfd->list_cb = callback;
+ gfd->user_data = user_data;
+
+ read_url_async (f, request, gfd);
+ g_free (request);
+}
+
+gchar *
+g_flickr_auth_getFrob (GFlickr *f)
+{
+ gchar *api_sig;
+ gchar *url;
+ GVfs *vfs;
+ GFile *uri;
+ gchar *contents;
+ GError *error = NULL;
+ gchar *frob = NULL;
+
+ g_return_val_if_fail (G_IS_FLICKR (f), NULL);
+
+ api_sig = get_api_sig (f->priv->auth_secret,
+ "api_key", f->priv->api_key,
+ "method", "flickr.auth.getFrob",
+ NULL);
+
+ /* Build url */
+ url = g_strdup_printf (FLICKR_AUTH_GETFROB,
+ f->priv->api_key,
+ api_sig);
+ g_free (api_sig);
+
+ /* Load content */
+ vfs = g_vfs_get_default ();
+ uri = g_vfs_get_file_for_uri (vfs, url);
+ g_free (url);
+ if (!g_file_load_contents (uri, NULL, &contents, NULL, NULL, &error)) {
+ GRL_WARNING ("Unable to get Flickr's frob: %s", error->message);
+ return NULL;
+ }
+
+ /* Get frob */
+ frob = get_xpath_element (contents, "/rsp/frob");
+ g_free (contents);
+ if (!frob) {
+ GRL_WARNING ("Can not get Flickr's frob");
+ }
+
+ return frob;
+}
+
+gchar *
+g_flickr_auth_loginLink (GFlickr *f,
+ const gchar *frob,
+ const gchar *perm)
+{
+ gchar *api_sig;
+ gchar *url;
+
+ g_return_val_if_fail (G_IS_FLICKR (f), NULL);
+ g_return_val_if_fail (frob, NULL);
+ g_return_val_if_fail (perm, NULL);
+
+ api_sig = get_api_sig (f->priv->auth_secret,
+ "api_key", f->priv->api_key,
+ "frob", frob,
+ "perms", perm,
+ NULL);
+
+ url = g_strdup_printf (FLICKR_AUTH_LOGINLINK,
+ f->priv->api_key,
+ api_sig,
+ frob,
+ perm);
+ g_free (api_sig);
+
+ return url;
+}
+
+gchar *
+g_flickr_auth_getToken (GFlickr *f,
+ const gchar *frob)
+{
+ GError *error = NULL;
+ GFile *uri;
+ GVfs *vfs;
+ gchar *api_sig;
+ gchar *contents;
+ gchar *token;
+ gchar *url;
+
+ g_return_val_if_fail (G_IS_FLICKR (f), NULL);
+ g_return_val_if_fail (frob, NULL);
+
+ api_sig = get_api_sig (f->priv->auth_secret,
+ "method", "flickr.auth.getToken",
+ "api_key", f->priv->api_key,
+ "frob", frob,
+ NULL);
+
+ /* Build url */
+ url = g_strdup_printf (FLICKR_AUTH_GETTOKEN,
+ f->priv->api_key,
+ api_sig,
+ frob);
+ g_free (api_sig);
+
+ /* Load content */
+ vfs = g_vfs_get_default ();
+ uri = g_vfs_get_file_for_uri (vfs, url);
+ g_free (url);
+ if (!g_file_load_contents (uri, NULL, &contents, NULL, NULL, &error)) {
+ GRL_WARNING ("Unable to get Flickr's token: %s", error->message);
+ return NULL;
+ }
+
+ /* Get token */
+ token = get_xpath_element (contents, "/rsp/auth/token");
+ g_free (contents);
+ if (!token) {
+ GRL_WARNING ("Can not get Flickr's token");
+ }
+
+ return token;
+}
+
+void
+g_flickr_auth_checkToken (GFlickr *f,
+ const gchar *token,
+ GFlickrHashTableCb callback,
+ gpointer user_data)
+{
+ gchar *api_sig;
+ gchar *request;
+
+ g_return_if_fail (G_IS_FLICKR (f));
+ g_return_if_fail (token);
+ g_return_if_fail (callback);
+
+ api_sig = get_api_sig (f->priv->auth_secret,
+ "method", FLICKR_AUTH_CHECKTOKEN_METHOD,
+ "api_key", f->priv->api_key,
+ "auth_token", token,
+ NULL);
+
+ /* Build request */
+ request = g_strdup_printf (FLICKR_AUTH_CHECKTOKEN,
+ f->priv->api_key,
+ api_sig,
+ token);
+ g_free (api_sig);
+
+ GFlickrData *gfd = g_slice_new (GFlickrData);
+ gfd->flickr = g_object_ref (f);
+ gfd->parse_xml = process_token_result;
+ gfd->hashtable_cb = callback;
+ gfd->user_data = user_data;
+
+ read_url_async (f, request, gfd);
+ g_free (request);
+}
diff --git a/src/media/flickr/gflickr.h b/src/media/flickr/gflickr.h
new file mode 100644
index 0000000..dd583d8
--- /dev/null
+++ b/src/media/flickr/gflickr.h
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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 _G_FLICKR_H_
+#define _G_FLICKR_H_
+
+#include <glib-object.h>
+
+#define G_FLICKR_TYPE \
+ (g_flickr_get_type ())
+
+#define G_FLICKR(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ G_FLICKR_TYPE, \
+ GFlickr))
+
+#define G_IS_FLICKR(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ G_FLICKR_TYPE))
+
+#define G_FLICKR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ G_FLICKR_TYPE, \
+ GFlickrClass))
+
+#define G_IS_FLICKR_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ G_FLICKR_TYPE))
+
+#define G_FLICKR_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ G_FLICKR_TYPE, \
+ GFlickrClass))
+
+typedef struct _GFlickr GFlickr;
+typedef struct _GFlickrPrivate GFlickrPrivate;
+
+struct _GFlickr {
+
+ GObject parent;
+
+ /*< private >*/
+ GFlickrPrivate *priv;
+};
+
+typedef struct _GFlickrClass GFlickrClass;
+
+struct _GFlickrClass {
+
+ GObjectClass parent_class;
+
+};
+
+
+typedef void (*GFlickrHashTableCb) (GFlickr *f, GHashTable *result, gpointer user_data);
+
+typedef void (*GFlickrListCb) (GFlickr *f, GList *result, gpointer user_data);
+
+typedef void (*GFlickrCheckToken) (GFlickr *f, GHashTable *tokeninfo, gpointer user_data);
+
+GType g_flickr_get_type (void);
+
+GFlickr *g_flickr_new (const gchar *api_key, const gchar *auth_secret, const gchar *auth_token);
+
+void g_flickr_set_per_page (GFlickr *f, gint per_page);
+
+void
+g_flickr_photos_getInfo (GFlickr *f,
+ glong photo_id,
+ GFlickrHashTableCb callback,
+ gpointer user_data);
+
+void
+g_flickr_photos_search (GFlickr *f,
+ const gchar *user_id,
+ const gchar *text,
+ const gchar *tags,
+ gint page,
+ GFlickrListCb callback,
+ gpointer user_data);
+
+void
+g_flickr_photos_getRecent (GFlickr *f,
+ gint page,
+ GFlickrListCb callback,
+ gpointer user_data);
+
+gchar *
+g_flickr_photo_url_original (GFlickr *f, GHashTable *photo);
+
+gchar *
+g_flickr_photo_url_thumbnail (GFlickr *f, GHashTable *photo);
+
+gchar *
+g_flickr_photo_url_largest (GFlickr *f, GHashTable *photo);
+
+void
+g_flickr_tags_getHotList (GFlickr *f,
+ gint count,
+ GFlickrListCb callback,
+ gpointer user_data);
+
+void
+g_flickr_photosets_getList (GFlickr *f,
+ const gchar *user_id,
+ GFlickrListCb callback,
+ gpointer user_data);
+
+void
+g_flickr_photosets_getPhotos (GFlickr *f,
+ const gchar *photoset_id,
+ gint page,
+ GFlickrListCb callback,
+ gpointer user_data);
+
+gchar *
+g_flickr_auth_getFrob (GFlickr *f);
+
+gchar *
+g_flickr_auth_loginLink (GFlickr *f,
+ const gchar *frob,
+ const gchar *perm);
+
+gchar *
+g_flickr_auth_getToken (GFlickr *f,
+ const gchar *frob);
+
+void
+g_flickr_auth_checkToken (GFlickr *f,
+ const gchar *token,
+ GFlickrHashTableCb callback,
+ gpointer user_data);
+
+#endif /* _G_FLICKR_H_ */
diff --git a/src/media/flickr/grl-flickr.c b/src/media/flickr/grl-flickr.c
new file mode 100644
index 0000000..6065355
--- /dev/null
+++ b/src/media/flickr/grl-flickr.c
@@ -0,0 +1,755 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "grl-flickr.h"
+#include "gflickr.h"
+
+#define GRL_FLICKR_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_FLICKR_SOURCE_TYPE, \
+ GrlFlickrSourcePrivate))
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT flickr_log_domain
+GRL_LOG_DOMAIN(flickr_log_domain);
+
+#define SEARCH_MAX 500
+#define HOTLIST_MAX 200
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID FLICKR_PLUGIN_ID
+
+#define PUBLIC_SOURCE_ID "grl-flickr"
+#define PUBLIC_SOURCE_NAME "Flickr"
+#define PUBLIC_SOURCE_DESC "A source for browsing and searching Flickr photos"
+
+#define PERSONAL_SOURCE_ID "grl-flickr-%s"
+#define PERSONAL_SOURCE_NAME "%s's Flickr"
+#define PERSONAL_SOURCE_DESC "A source for browsing and searching %s' flickr photos"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+typedef struct {
+ GrlMediaSource *source;
+ GrlMediaSourceResultCb callback;
+ gchar *user_id;
+ gchar *tags;
+ gchar *text;
+ guint offset;
+ guint page;
+ gpointer user_data;
+ guint count;
+ guint operation_id;
+} OperationData;
+
+struct _GrlFlickrSourcePrivate {
+ GFlickr *flickr;
+ gchar *user_id;
+};
+
+static void token_info_cb (GFlickr *f,
+ GHashTable *info,
+ gpointer user_data);
+
+static GrlFlickrSource *grl_flickr_source_public_new (const gchar *flickr_api_key,
+ const gchar *flickr_secret);
+
+static void grl_flickr_source_personal_new (const GrlPluginInfo *plugin,
+ const gchar *flickr_api_key,
+ const gchar *flickr_secret,
+ const gchar *flickr_token);
+
+static void grl_flickr_source_finalize (GObject *object);
+
+gboolean grl_flickr_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+
+static const GList *grl_flickr_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_flickr_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+
+static void grl_flickr_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ss);
+
+static void grl_flickr_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+
+/* =================== Flickr Plugin =============== */
+
+gboolean
+grl_flickr_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ gchar *flickr_key;
+ gchar *flickr_secret;
+ gchar *flickr_token;
+ GrlConfig *config;
+ gboolean public_source_created = FALSE;
+
+ GRL_LOG_DOMAIN_INIT (flickr_log_domain, "flickr");
+
+ GRL_DEBUG ("flickr_plugin_init");
+
+ if (!configs) {
+ GRL_WARNING ("Missing configuration");
+ return FALSE;
+ }
+
+ while (configs) {
+ config = GRL_CONFIG (configs->data);
+
+ flickr_key = grl_config_get_api_key (config);
+ flickr_token = grl_config_get_api_token (config);
+ flickr_secret = grl_config_get_api_secret (config);
+
+ if (!flickr_key || !flickr_secret) {
+ GRL_WARNING ("Required configuration keys not set up");
+ } else if (flickr_token) {
+ grl_flickr_source_personal_new (plugin,
+ flickr_key,
+ flickr_secret,
+ flickr_token);
+ } else if (public_source_created) {
+ GRL_WARNING ("Only one public source can be created");
+ } else {
+ GrlFlickrSource *source = grl_flickr_source_public_new (flickr_key, flickr_secret);
+ public_source_created = TRUE;
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ }
+
+ if (flickr_key != NULL)
+ g_free (flickr_key);
+ if (flickr_token != NULL)
+ g_free (flickr_token);
+ if (flickr_secret != NULL)
+ g_free (flickr_secret);
+
+ configs = g_list_next (configs);
+ }
+
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_flickr_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Flickr GObject ================ */
+
+G_DEFINE_TYPE (GrlFlickrSource, grl_flickr_source, GRL_TYPE_MEDIA_SOURCE);
+
+static GrlFlickrSource *
+grl_flickr_source_public_new (const gchar *flickr_api_key,
+ const gchar *flickr_secret)
+{
+ GrlFlickrSource *source;
+
+ GRL_DEBUG ("grl_flickr_source_new");
+
+ source = g_object_new (GRL_FLICKR_SOURCE_TYPE,
+ "source-id", PUBLIC_SOURCE_ID,
+ "source-name", PUBLIC_SOURCE_NAME,
+ "source-desc", PUBLIC_SOURCE_DESC,
+ NULL);
+ source->priv->flickr = g_flickr_new (flickr_api_key, flickr_secret, NULL);
+
+ return source;
+}
+
+static void
+grl_flickr_source_personal_new (const GrlPluginInfo *plugin,
+ const gchar *flickr_api_key,
+ const gchar *flickr_secret,
+ const gchar *flickr_token)
+{
+ GFlickr *f;
+
+ f = g_flickr_new (flickr_api_key, flickr_secret, flickr_token);
+ g_flickr_auth_checkToken (f, flickr_token, token_info_cb, (gpointer) plugin);
+}
+
+static void
+grl_flickr_source_class_init (GrlFlickrSourceClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+
+ gobject_class->finalize = grl_flickr_source_finalize;
+ source_class->browse = grl_flickr_source_browse;
+ source_class->metadata = grl_flickr_source_metadata;
+ source_class->search = grl_flickr_source_search;
+ metadata_class->supported_keys = grl_flickr_source_supported_keys;
+
+ g_type_class_add_private (klass, sizeof (GrlFlickrSourcePrivate));
+}
+
+static void
+grl_flickr_source_init (GrlFlickrSource *source)
+{
+ source->priv = GRL_FLICKR_SOURCE_GET_PRIVATE (source);
+
+ grl_media_source_set_auto_split_threshold (GRL_MEDIA_SOURCE (source),
+ SEARCH_MAX);
+}
+
+static void
+grl_flickr_source_finalize (GObject *object)
+{
+ GrlFlickrSource *source;
+
+ GRL_DEBUG ("grl_flickr_source_finalize");
+
+ source = GRL_FLICKR_SOURCE (object);
+ g_free (source->priv->user_id);
+
+ G_OBJECT_CLASS (grl_flickr_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static void
+token_info_cb (GFlickr *f,
+ GHashTable *info,
+ gpointer user_data)
+{
+ GrlFlickrSource *source;
+ GrlPluginInfo *plugin = (GrlPluginInfo *) user_data;
+ GrlPluginRegistry *registry;
+ gchar *fullname;
+ gchar *source_desc;
+ gchar *source_id;
+ gchar *source_name;
+ gchar *username;
+
+ if (!info) {
+ GRL_WARNING ("Wrong token!");
+ g_object_unref (f);
+ return;
+ }
+
+ registry = grl_plugin_registry_get_default ();
+
+ username = g_hash_table_lookup (info, "user_username");
+ fullname = g_hash_table_lookup (info, "user_fullname");
+
+ source_id = g_strdup_printf (PERSONAL_SOURCE_ID, username);
+ source_name = g_strdup_printf (PERSONAL_SOURCE_NAME, fullname);
+ source_desc = g_strdup_printf (PERSONAL_SOURCE_DESC, fullname);
+
+ /* Check if source is already registered */
+ if (grl_plugin_registry_lookup_source (registry, source_id)) {
+ GRL_DEBUG ("A source with id '%s' is already registered. Skipping...",
+ source_id);
+ g_object_unref (f);
+ } else {
+ source = g_object_new (GRL_FLICKR_SOURCE_TYPE,
+ "source-id", source_id,
+ "source-name", source_name,
+ "source-desc", source_desc,
+ NULL);
+ source->priv->flickr = f;
+ source->priv->user_id = g_strdup (g_hash_table_lookup (info, "user_nsid"));
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ }
+
+ g_free (source_id);
+ g_free (source_name);
+ g_free (source_desc);
+}
+
+static void
+update_media (GrlMedia *media, GHashTable *photo)
+{
+ gchar *author;
+ gchar *date;
+ gchar *description;
+ gchar *id;
+ gchar *thumbnail;
+ gchar *title;
+ gchar *url;
+
+ author = g_hash_table_lookup (photo, "owner_realname");
+ if (!author) {
+ author = g_hash_table_lookup (photo, "photo_ownername");
+ }
+ date = g_hash_table_lookup (photo, "dates_taken");
+ if (!date) {
+ date = g_hash_table_lookup (photo, "photo_datetaken");
+ }
+ description = g_hash_table_lookup (photo, "description");
+ id = g_hash_table_lookup (photo, "photo_id");
+ thumbnail = g_strdup (g_hash_table_lookup (photo, "photo_url_t"));
+ if (!thumbnail) {
+ thumbnail = g_flickr_photo_url_thumbnail (NULL, photo);
+ }
+ title = g_hash_table_lookup (photo, "title");
+ if (!title) {
+ title = g_hash_table_lookup (photo, "photo_title");
+ }
+ url = g_strdup (g_hash_table_lookup (photo, "photo_url_o"));
+ if (!url) {
+ url = g_flickr_photo_url_original (NULL, photo);
+ if (!url) {
+ url = g_flickr_photo_url_largest (NULL, photo);
+ }
+ }
+
+ if (author) {
+ grl_media_set_author (media, author);
+ }
+
+ if (date) {
+ grl_media_set_date (media, date);
+ }
+
+ if (description && description[0] != '\0') {
+ grl_media_set_description (media, description);
+ }
+
+ if (id) {
+ grl_media_set_id (media, id);
+ }
+
+ if (thumbnail) {
+ grl_media_set_thumbnail (media, thumbnail);
+ g_free (thumbnail);
+ }
+
+ if (title && title[0] != '\0') {
+ grl_media_set_title (media, title);
+ }
+
+ if (url) {
+ grl_media_set_url (media, url);
+ g_free (url);
+ }
+}
+
+static void
+getInfo_cb (GFlickr *f, GHashTable *photo, gpointer user_data)
+{
+ GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
+
+ if (photo) {
+ update_media (ms->media, photo);
+ }
+
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+}
+
+static void
+search_cb (GFlickr *f, GList *photolist, gpointer user_data)
+{
+ GrlMedia *media;
+ OperationData *od = (OperationData *) user_data;
+ gchar *media_type;
+
+ /* Go to offset element */
+ photolist = g_list_nth (photolist, od->offset);
+
+ /* No more elements can be sent */
+ if (!photolist) {
+ od->callback (od->source,
+ od->operation_id,
+ NULL,
+ 0,
+ od->user_data,
+ NULL);
+ g_slice_free (OperationData, od);
+ return;
+ }
+
+ while (photolist && od->count) {
+ media_type = g_hash_table_lookup (photolist->data, "photo_media");
+ if (strcmp (media_type, "photo") == 0) {
+ media = grl_media_image_new ();
+ } else {
+ media = grl_media_video_new ();
+ }
+ update_media (media, photolist->data);
+ od->callback (od->source,
+ od->operation_id,
+ media,
+ od->count == 1? 0: -1,
+ od->user_data,
+ NULL);
+ photolist = g_list_next (photolist);
+ od->count--;
+ }
+
+ /* Get more elements */
+ if (od->count) {
+ od->offset = 0;
+ od->page++;
+ g_flickr_photos_search (f,
+ od->user_id,
+ od->text,
+ od->tags,
+ od->page,
+ search_cb,
+ od);
+ } else {
+ g_slice_free (OperationData, od);
+ }
+}
+
+static void
+photosetslist_cb (GFlickr *f, GList *photosets, gpointer user_data)
+{
+ GrlMedia *media;
+ GrlMediaSourceBrowseSpec *bs = (GrlMediaSourceBrowseSpec *) user_data;
+ gchar *value;
+ gint count;
+
+ /* Go to offset element */
+ photosets = g_list_nth (photosets, bs->skip);
+
+ /* No more elements can be sent */
+ if (!photosets) {
+ bs->callback (bs->source,
+ bs->browse_id,
+ NULL,
+ 0,
+ bs->user_data,
+ NULL);
+ return;
+ }
+
+ /* Send data */
+ count = g_list_length (photosets);
+ if (count > bs->count) {
+ count = bs->count;
+ }
+
+ while (photosets && count > 0) {
+ count--;
+ media = grl_media_box_new ();
+ grl_media_set_id (media,
+ g_hash_table_lookup (photosets->data,
+ "photoset_id"));
+ value = g_hash_table_lookup (photosets->data, "title");
+ if (value && value[0] != '\0') {
+ grl_media_set_title (media, value);
+ }
+ value = g_hash_table_lookup (photosets->data, "description");
+ if (value && value[0] != '\0') {
+ grl_media_set_description (media, value);
+ }
+
+ bs->callback (bs->source,
+ bs->browse_id,
+ media,
+ count,
+ bs->user_data,
+ NULL);
+ photosets = g_list_next (photosets);
+ }
+}
+
+static void
+photosetsphotos_cb (GFlickr *f, GList *photolist, gpointer user_data)
+{
+ GrlMedia *media;
+ OperationData *od = (OperationData *) user_data;
+ gchar *media_type;
+
+ /* Go to offset element */
+ photolist = g_list_nth (photolist, od->offset);
+
+ /* No more elements can be sent */
+ if (!photolist) {
+ od->callback (od->source,
+ od->operation_id,
+ NULL,
+ 0,
+ od->user_data,
+ NULL);
+ return;
+ }
+
+ while (photolist && od->count) {
+ media_type = g_hash_table_lookup (photolist->data, "photo_media");
+ if (strcmp (media_type, "photo") == 0) {
+ media = grl_media_image_new ();
+ } else {
+ media = grl_media_video_new ();
+ }
+
+ update_media (media, photolist->data);
+ od->callback (od->source,
+ od->operation_id,
+ media,
+ od->count == 1? 0: -1,
+ od->user_data,
+ NULL);
+ photolist = g_list_next (photolist);
+ od->count--;
+ }
+
+ /* Get more elements */
+ if (od->count) {
+ od->offset = 0;
+ od->page++;
+ g_flickr_photosets_getPhotos (f, od->text, od->page, photosetsphotos_cb, od);
+ } else {
+ g_slice_free (OperationData, od);
+ }
+}
+
+static void
+gettags_cb (GFlickr *f, GList *taglist, gpointer user_data)
+{
+ GrlMedia *media;
+ GrlMediaSourceBrowseSpec *bs = (GrlMediaSourceBrowseSpec *) user_data;
+ gint count;
+
+ /* Go to offset element */
+ taglist = g_list_nth (taglist, bs->skip);
+
+ /* No more elements can be sent */
+ if (!taglist) {
+ bs->callback (bs->source,
+ bs->browse_id,
+ NULL,
+ 0,
+ bs->user_data,
+ NULL);
+ return;
+ }
+
+ /* Send data */
+ count = g_list_length (taglist);
+ while (taglist) {
+ count--;
+ media = grl_media_box_new ();
+ grl_media_set_id (media, taglist->data);
+ grl_media_set_title (media, taglist->data);
+ bs->callback (bs->source,
+ bs->browse_id,
+ media,
+ count,
+ bs->user_data,
+ NULL);
+ taglist = g_list_next (taglist);
+ }
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_flickr_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_AUTHOR,
+ GRL_METADATA_KEY_DATE,
+ GRL_METADATA_KEY_DESCRIPTION,
+ GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_THUMBNAIL,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_flickr_source_public_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ GFlickr *f = GRL_FLICKR_SOURCE (source)->priv->flickr;
+ const gchar *container_id;
+ guint per_page;
+ gint request_size;
+
+ container_id = grl_media_get_id (bs->container);
+
+ if (!container_id) {
+ /* Get hot tags list. List is limited up to HOTLIST_MAX tags */
+ if (bs->skip >= HOTLIST_MAX) {
+ bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, NULL);
+ } else {
+ request_size = CLAMP (bs->skip + bs->count, 1, HOTLIST_MAX);
+ g_flickr_tags_getHotList (f, request_size, gettags_cb, bs);
+ }
+ } else {
+ OperationData *od = g_slice_new (OperationData);
+
+ grl_paging_translate (bs->skip,
+ bs->count,
+ SEARCH_MAX,
+ &per_page,
+ &(od->page),
+ &(od->offset));
+ g_flickr_set_per_page (f, per_page);
+
+ od->source = bs->source;
+ od->callback = bs->callback;
+ od->user_id = GRL_FLICKR_SOURCE (source)->priv->user_id;
+ od->tags = (gchar *) container_id;
+ od->text = NULL;
+ od->user_data = bs->user_data;
+ od->count = bs->count;
+ od->operation_id = bs->browse_id;
+ g_flickr_photos_search (f,
+ od->user_id,
+ NULL,
+ od->tags,
+ od->page,
+ search_cb,
+ od);
+ }
+}
+
+static void
+grl_flickr_source_personal_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ GFlickr *f = GRL_FLICKR_SOURCE (source)->priv->flickr;
+ OperationData *od;
+ const gchar *container_id;
+ guint per_page;
+
+ container_id = grl_media_get_id (bs->container);
+
+ if (!container_id) {
+ /* Get photoset */
+ g_flickr_photosets_getList (f, NULL, photosetslist_cb, bs);
+ } else {
+ od = g_slice_new (OperationData);
+
+ /* Compute items per page and page offset */
+ grl_paging_translate (bs->skip,
+ bs->count,
+ SEARCH_MAX,
+ &per_page,
+ &(od->page),
+ &(od->offset));
+ g_flickr_set_per_page (f, per_page);
+ od->source = bs->source;
+ od->callback = bs->callback;
+ od->tags = NULL;
+ od->text = (gchar *) container_id;
+ od->user_data = bs->user_data;
+ od->count = bs->count;
+ od->operation_id = bs->browse_id;
+
+ g_flickr_photosets_getPhotos (f, container_id, od->page, photosetsphotos_cb, od);
+ }
+}
+
+static void
+grl_flickr_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ if (GRL_FLICKR_SOURCE (source)->priv->user_id) {
+ grl_flickr_source_personal_browse (source, bs);
+ } else {
+ grl_flickr_source_public_browse (source, bs);
+ }
+}
+
+static void
+grl_flickr_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ const gchar *id;
+
+ if (!ms->media || (id = grl_media_get_id (ms->media)) == NULL) {
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ return;
+ }
+
+ g_flickr_photos_getInfo (GRL_FLICKR_SOURCE (source)->priv->flickr,
+ atol (id),
+ getInfo_cb,
+ ms);
+}
+
+static void
+grl_flickr_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss)
+{
+ GFlickr *f = GRL_FLICKR_SOURCE (source)->priv->flickr;
+ guint per_page;
+ OperationData *od = g_slice_new (OperationData);
+
+ /* Compute items per page and page offset */
+ grl_paging_translate (ss->skip,
+ ss->count,
+ SEARCH_MAX,
+ &per_page,
+ &(od->page),
+ &(od->offset));
+ g_flickr_set_per_page (f, per_page);
+
+ od->source = ss->source;
+ od->callback = ss->callback;
+ od->user_id = GRL_FLICKR_SOURCE (source)->priv->user_id;
+ od->tags = NULL;
+ od->text = ss->text;
+ od->user_data = ss->user_data;
+ od->count = ss->count;
+ od->operation_id = ss->search_id;
+
+ if (od->user_id || od->text) {
+ g_flickr_photos_search (f,
+ od->user_id,
+ ss->text,
+ NULL,
+ od->page,
+ search_cb,
+ od);
+ } else {
+ g_flickr_photos_getRecent (f,
+ od->page,
+ search_cb,
+ od);
+ }
+}
diff --git a/src/media/flickr/grl-flickr.h b/src/media/flickr/grl-flickr.h
new file mode 100644
index 0000000..af1a843
--- /dev/null
+++ b/src/media/flickr/grl-flickr.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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_FLICKR_SOURCE_H_
+#define _GRL_FLICKR_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_FLICKR_SOURCE_TYPE \
+ (grl_flickr_source_get_type ())
+
+#define GRL_FLICKR_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_FLICKR_SOURCE_TYPE, \
+ GrlFlickrSource))
+
+#define GRL_IS_FLICKR_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_FLICKR_SOURCE_TYPE))
+
+#define GRL_FLICKR_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_FLICKR_SOURCE_TYPE, \
+ GrlFlickrSourceClass))
+
+#define GRL_IS_FLICKR_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_FLICKR_SOURCE_TYPE))
+
+#define GRL_FLICKR_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_FLICKR_SOURCE_TYPE, \
+ GrlFlickrSourceClass))
+
+/* plugin's log domain */
+GRL_LOG_DOMAIN_EXTERN(flickr_log_domain);
+
+typedef struct _GrlFlickrSource GrlFlickrSource;
+typedef struct _GrlFlickrSourcePrivate GrlFlickrSourcePrivate;
+
+struct _GrlFlickrSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlFlickrSourcePrivate *priv;
+};
+
+typedef struct _GrlFlickrSourceClass GrlFlickrSourceClass;
+
+struct _GrlFlickrSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_flickr_source_get_type (void);
+
+#endif /* _GRL_FLICKR_SOURCE_H_ */
diff --git a/src/media/flickr/grl-flickr.xml b/src/media/flickr/grl-flickr.xml
new file mode 100644
index 0000000..4b47f05
--- /dev/null
+++ b/src/media/flickr/grl-flickr.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Flickr</name>
+ <description>A plugin for browsing and searching Flickr photos</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/jamendo/Makefile.am b/src/media/jamendo/Makefile.am
new file mode 100644
index 0000000..3232ac2
--- /dev/null
+++ b/src/media/jamendo/Makefile.am
@@ -0,0 +1,38 @@
+#
+# Makefile.am
+#
+# Author: Juan A. Suarez Romero <jasuarez igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrljamendo.la
+
+libgrljamendo_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GRLNET_CFLAGS) \
+ $(XML_CFLAGS)
+
+libgrljamendo_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GRLNET_LIBS) \
+ $(XML_LIBS)
+
+libgrljamendo_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrljamendo_la_SOURCES = \
+ grl-jamendo.c \
+ grl-jamendo.h
+
+libdir = $(GRL_PLUGINS_DIR)
+jamendoxmldir = $(GRL_PLUGINS_CONF_DIR)
+jamendoxml_DATA = $(JAMENDO_PLUGIN_ID).xml
+
+EXTRA_DIST = $(jamendoxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/jamendo/TODO b/src/media/jamendo/TODO
new file mode 100644
index 0000000..2571947
--- /dev/null
+++ b/src/media/jamendo/TODO
@@ -0,0 +1,30 @@
+Get global database and work locally
+====================================
+Actually all queries are on-line. But the whole database can be retrieved and
+stored in local, so all operation can be performed off-line.
+
+Doing it requires to implement the search in local. We should explore if either
+moving to this approach or even creating a new derivative plugin that follows
+this off-line approach worths it.
+
+
+Implement childcount
+====================
+Implementing childcount would require, for each result doing another search and
+retrieve all children, counting them.
+
+
+Limit search-rate
+=================
+Jamendo terms of use states that applications should be limited to 1 query per
+second. Some kind of control should be needed in the plugin.
+
+
+Limit requested elements
+========================
+User can request any arbitrary number of elements, and this request is propagated to Jamendo.
+
+Nevertheless, it would be better to break the request in smaller chunks, so
+though user requests, for instance, 1000 albums, we do the query in steps of 25
+elements.
+
diff --git a/src/media/jamendo/grl-jamendo.c b/src/media/jamendo/grl-jamendo.c
new file mode 100644
index 0000000..f6ba5c6
--- /dev/null
+++ b/src/media/jamendo/grl-jamendo.c
@@ -0,0 +1,1365 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
+#include <net/grl-net.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+#include <string.h>
+
+#include "grl-jamendo.h"
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT jamendo_log_domain
+GRL_LOG_DOMAIN_STATIC(jamendo_log_domain);
+
+#define JAMENDO_ID_SEP "/"
+#define JAMENDO_ROOT_NAME "Jamendo"
+
+#define MAX_ELEMENTS 100
+
+/* ------- Categories ------- */
+
+#define JAMENDO_ARTIST "artist"
+#define JAMENDO_ALBUM "album"
+#define JAMENDO_TRACK "track"
+
+/* ---- Jamendo Web API ---- */
+
+#define JAMENDO_BASE_ENTRY "http://api.jamendo.com/get2"
+#define JAMENDO_FORMAT "xml"
+#define JAMENDO_RANGE "n=%u&pn=%u"
+
+#define JAMENDO_ARTIST_ENTRY JAMENDO_BASE_ENTRY "/%s/" JAMENDO_ARTIST "/" JAMENDO_FORMAT
+
+#define JAMENDO_ALBUM_ENTRY JAMENDO_BASE_ENTRY "/%s/" JAMENDO_ALBUM "/" JAMENDO_FORMAT \
+ "/" JAMENDO_ALBUM "_" JAMENDO_ARTIST
+
+#define JAMENDO_TRACK_ENTRY JAMENDO_BASE_ENTRY "/%s/" JAMENDO_TRACK "/" JAMENDO_FORMAT \
+ "/" JAMENDO_ALBUM "_" JAMENDO_ARTIST "+" JAMENDO_TRACK "_" JAMENDO_ALBUM
+
+#define JAMENDO_GET_ARTISTS JAMENDO_ARTIST_ENTRY "/?" JAMENDO_RANGE
+#define JAMENDO_GET_ALBUMS JAMENDO_ALBUM_ENTRY "/?" JAMENDO_RANGE
+#define JAMENDO_GET_TRACKS JAMENDO_TRACK_ENTRY "/?" JAMENDO_RANGE
+
+#define JAMENDO_GET_ALBUMS_FROM_ARTIST JAMENDO_ALBUM_ENTRY "/?" JAMENDO_RANGE "&artist_id=%s"
+#define JAMENDO_GET_TRACKS_FROM_ALBUM JAMENDO_TRACK_ENTRY "/?" JAMENDO_RANGE "&album_id=%s"
+#define JAMENDO_GET_ARTIST JAMENDO_ARTIST_ENTRY "/?id=%s"
+
+#define JAMENDO_GET_ALBUM JAMENDO_ALBUM_ENTRY "/?id=%s"
+#define JAMENDO_GET_TRACK JAMENDO_TRACK_ENTRY "/?id=%s"
+
+#define JAMENDO_SEARCH_ARTIST JAMENDO_ARTIST_ENTRY "/?" JAMENDO_RANGE "&searchquery=%s"
+#define JAMENDO_SEARCH_ALBUM JAMENDO_ALBUM_ENTRY "/?" JAMENDO_RANGE "&searchquery=%s"
+#define JAMENDO_SEARCH_TRACK JAMENDO_TRACK_ENTRY "/?" JAMENDO_RANGE "&searchquery=%s"
+#define JAMENDO_SEARCH_ALL JAMENDO_TRACK_ENTRY "/?" JAMENDO_RANGE
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID JAMENDO_PLUGIN_ID
+
+#define SOURCE_ID "grl-jamendo"
+#define SOURCE_NAME "Jamendo"
+#define SOURCE_DESC "A source for browsing and searching Jamendo videos"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+enum {
+ METADATA,
+ BROWSE,
+ QUERY,
+ SEARCH
+};
+
+typedef enum {
+ JAMENDO_ARTIST_CAT = 1,
+ JAMENDO_ALBUM_CAT,
+ JAMENDO_FEEDS_CAT,
+ JAMENDO_TRACK_CAT,
+} JamendoCategory;
+
+typedef struct {
+ JamendoCategory category;
+ gchar *id;
+ gchar *artist_name;
+ gchar *artist_genre;
+ gchar *artist_url;
+ gchar *artist_image;
+ gchar *album_name;
+ gchar *album_genre;
+ gchar *album_url;
+ gchar *album_duration;
+ gchar *album_image;
+ gchar *track_name;
+ gchar *track_url;
+ gchar *track_stream;
+ gchar *track_duration;
+ gchar *feed_name;
+} Entry;
+
+typedef struct {
+ gint type;
+ union {
+ GrlMediaSourceBrowseSpec *bs;
+ GrlMediaSourceQuerySpec *qs;
+ GrlMediaSourceMetadataSpec *ms;
+ GrlMediaSourceSearchSpec *ss;
+ } spec;
+ xmlNodePtr node;
+ xmlDocPtr doc;
+ guint total_results;
+ guint index;
+ guint offset;
+ gboolean cancelled;
+} XmlParseEntries;
+
+struct Feeds {
+ gchar *name;
+ JamendoCategory cat;
+ gchar *url;
+} feeds[] = {
+ { "Albums of the week", JAMENDO_ALBUM_CAT,
+ JAMENDO_GET_ALBUMS "&order=ratingweek_desc", },
+ { "Tracks of the week", JAMENDO_TRACK_CAT,
+ JAMENDO_GET_TRACKS "&order=ratingweek_desc", },
+ { "New releases", JAMENDO_TRACK_CAT,
+ JAMENDO_GET_TRACKS "&order=releasedate_desc", },
+ { "Top artists", JAMENDO_ARTIST_CAT,
+ JAMENDO_GET_ARTISTS "&order=rating_desc", },
+ { "Top albums", JAMENDO_ALBUM_CAT,
+ JAMENDO_GET_ALBUMS "&order=rating_desc", },
+ { "Top tracks", JAMENDO_TRACK_CAT,
+ JAMENDO_GET_TRACKS "&order=rating_desc", },
+};
+
+struct _GrlJamendoSourcePriv {
+ GrlNetWc *wc;
+ GCancellable *cancellable;
+};
+
+#define GRL_JAMENDO_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_JAMENDO_SOURCE_TYPE, \
+ GrlJamendoSourcePriv))
+
+static GrlJamendoSource *grl_jamendo_source_new (void);
+
+gboolean grl_jamendo_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+static const GList *grl_jamendo_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_jamendo_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms);
+
+static void grl_jamendo_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+
+static void grl_jamendo_source_query (GrlMediaSource *source,
+ GrlMediaSourceQuerySpec *qs);
+
+static void grl_jamendo_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+
+static void grl_jamendo_source_cancel (GrlMediaSource *source,
+ guint operation_id);
+
+/* =================== Jamendo Plugin =============== */
+
+gboolean
+grl_jamendo_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (jamendo_log_domain, "jamendo");
+
+ GRL_DEBUG ("jamendo_plugin_init");
+
+ GrlJamendoSource *source = grl_jamendo_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_jamendo_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Jamendo GObject ================ */
+
+static GrlJamendoSource *
+grl_jamendo_source_new (void)
+{
+ GRL_DEBUG ("grl_jamendo_source_new");
+ return g_object_new (GRL_JAMENDO_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+G_DEFINE_TYPE (GrlJamendoSource, grl_jamendo_source, GRL_TYPE_MEDIA_SOURCE);
+
+static void
+grl_jamendo_source_finalize (GObject *object)
+{
+ GrlJamendoSource *self;
+
+ self = GRL_JAMENDO_SOURCE (object);
+ if (self->priv->wc)
+ g_object_unref (self->priv->wc);
+
+ if (self->priv->cancellable
+ && G_IS_CANCELLABLE (self->priv->cancellable))
+ g_object_unref (self->priv->cancellable);
+
+ G_OBJECT_CLASS (grl_jamendo_source_parent_class)->finalize (object);
+}
+
+static void
+grl_jamendo_source_class_init (GrlJamendoSourceClass * klass)
+{
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ GObjectClass *g_class = G_OBJECT_CLASS (klass);
+ source_class->metadata = grl_jamendo_source_metadata;
+ source_class->browse = grl_jamendo_source_browse;
+ source_class->query = grl_jamendo_source_query;
+ source_class->search = grl_jamendo_source_search;
+ source_class->cancel = grl_jamendo_source_cancel;
+ metadata_class->supported_keys = grl_jamendo_source_supported_keys;
+ g_class->finalize = grl_jamendo_source_finalize;
+
+ g_type_class_add_private (klass, sizeof (GrlJamendoSourcePriv));
+}
+
+static void
+grl_jamendo_source_init (GrlJamendoSource *source)
+{
+ source->priv = GRL_JAMENDO_SOURCE_GET_PRIVATE (source);
+
+ /* If we try to get too much elements in a single step, Jamendo might return
+ nothing. So limit the maximum amount of elements in each query */
+ grl_media_source_set_auto_split_threshold (GRL_MEDIA_SOURCE (source),
+ MAX_ELEMENTS);
+}
+
+/* ======================= Utilities ==================== */
+
+#if 0
+static void
+print_entry (Entry *entry)
+{
+ g_print ("Entry Information:\n");
+ g_print (" ID: %s\n", entry->id);
+ g_print (" Artist Name: %s\n", entry->artist_name);
+ g_print (" Artist Genre: %s\n", entry->artist_genre);
+ g_print (" Artist URL: %s\n", entry->artist_url);
+ g_print (" Artist Image: %s\n", entry->artist_image);
+ g_print (" Album Name: %s\n", entry->album_name);
+ g_print (" Album Genre: %s\n", entry->album_genre);
+ g_print (" Album URL: %s\n", entry->album_url);
+ g_print ("Album Duration: %s\n", entry->album_duration);
+ g_print (" Album Image: %s\n", entry->album_image);
+ g_print (" Track Name: %s\n", entry->track_name);
+ g_print (" Track URL: %s\n", entry->track_url);
+ g_print (" Track Stream: %s\n", entry->track_stream);
+ g_print ("Track Duration: %s\n", entry->track_duration);
+ g_print (" Feed Name: %s\n", entry->feed_name);
+}
+#endif
+
+static void
+free_entry (Entry *entry)
+{
+ g_free (entry->id);
+ g_free (entry->artist_name);
+ g_free (entry->artist_genre);
+ g_free (entry->artist_url);
+ g_free (entry->artist_image);
+ g_free (entry->album_name);
+ g_free (entry->album_genre);
+ g_free (entry->album_url);
+ g_free (entry->album_duration);
+ g_free (entry->album_image);
+ g_free (entry->track_name);
+ g_free (entry->track_url);
+ g_free (entry->track_stream);
+ g_free (entry->track_duration);
+ g_free (entry->feed_name);
+ g_slice_free (Entry, entry);
+}
+
+static gint
+xml_count_children (xmlNodePtr node)
+{
+#if (LIBXML2_VERSION >= 20700)
+ return xmlChildElementCount (node);
+#else
+ gint nchildren = 0;
+ xmlNodePtr i = node->xmlChildrenNode;
+
+ while (i) {
+ nchildren++;
+ i = i->next;
+ }
+
+ return nchildren;
+#endif
+}
+
+static void
+xml_parse_result (const gchar *str, GError **error, XmlParseEntries *xpe)
+{
+ xmlDocPtr doc;
+ xmlNodePtr node;
+ gint child_nodes = 0;
+
+ doc = xmlReadMemory (str, strlen (str), NULL, NULL,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ if (!doc) {
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Failed to parse Jamendo's response");
+ goto free_resources;
+ }
+
+ node = xmlDocGetRootElement (doc);
+ if (!node) {
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Empty response from Jamendo");
+ goto free_resources;
+ }
+
+ if (xmlStrcmp (node->name, (const xmlChar *) "data")) {
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Unexpected response from Jamendo: no data");
+ goto free_resources;
+ }
+
+ child_nodes = xml_count_children (node);
+ node = node->xmlChildrenNode;
+
+ /* Skip offset */
+ while (node && xpe->offset > 0) {
+ node = node->next;
+ child_nodes--;
+ xpe->offset--;
+ }
+
+ xpe->node = node;
+ xpe->doc = doc;
+ xpe->total_results = child_nodes;
+
+ return;
+
+ free_resources:
+ xmlFreeDoc (doc);
+}
+
+static Entry *
+xml_parse_entry (xmlDocPtr doc, xmlNodePtr entry)
+{
+ xmlNodePtr node;
+ xmlNs *ns;
+ Entry *data = g_slice_new0 (Entry);
+
+ if (strcmp ((gchar *) entry->name, JAMENDO_ARTIST) == 0) {
+ data->category = JAMENDO_ARTIST_CAT;
+ } else if (strcmp ((gchar *) entry->name, JAMENDO_ALBUM) == 0) {
+ data->category = JAMENDO_ALBUM_CAT;
+ } else if (strcmp ((gchar *) entry->name, JAMENDO_TRACK) == 0) {
+ data->category = JAMENDO_TRACK_CAT;
+ } else {
+ g_return_val_if_reached (NULL);
+ }
+
+ node = entry->xmlChildrenNode;
+
+ while (node) {
+ ns = node->ns;
+
+ if (!xmlStrcmp (node->name, (const xmlChar *) "id")) {
+ data->id =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "artist_name")) {
+ data->artist_name =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_name")) {
+ data->album_name =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "artist_genre")) {
+ data->artist_genre =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "artist_url")) {
+ data->artist_url =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "artist_image")) {
+ data->artist_image =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_genre")) {
+ data->album_genre =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_url")) {
+ data->album_url =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_duration")) {
+ data->album_duration =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "album_image")) {
+ data->album_image =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "track_name")) {
+ data->track_name =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "track_url")) {
+ data->track_url =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "track_stream")) {
+ data->track_stream =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "track_duration")) {
+ data->track_duration =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ }
+
+ node = node->next;
+ }
+
+ return data;
+}
+
+static void
+update_media_from_entry (GrlMedia *media, const Entry *entry)
+{
+ gchar *id;
+
+ if (entry->id) {
+ id = g_strdup_printf ("%d/%s", entry->category, entry->id);
+ } else {
+ id = g_strdup_printf ("%d", entry->category);
+ }
+
+ /* Common fields */
+ grl_media_set_id (media, id);
+ g_free (id);
+
+ if (entry->artist_name) {
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_ARTIST,
+ entry->artist_name);
+ }
+
+ if (entry->album_name) {
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_ALBUM,
+ entry->album_name);
+ }
+
+ /* Fields for artist */
+ if (entry->category == JAMENDO_ARTIST_CAT) {
+ if (entry->artist_name) {
+ grl_media_set_title (media, entry->artist_name);
+ }
+
+ if (entry->artist_genre) {
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_GENRE,
+ entry->artist_genre);
+ }
+
+ if (entry->artist_url) {
+ grl_media_set_site (media, entry->artist_url);
+ }
+
+ if (entry->artist_image) {
+ grl_media_set_thumbnail (media, entry->artist_image);
+ }
+
+ /* Fields for album */
+ } else if (entry->category == JAMENDO_ALBUM_CAT) {
+ if (entry->album_name) {
+ grl_media_set_title (media, entry->album_name);
+ }
+
+ if (entry->album_genre) {
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_GENRE,
+ entry->album_genre);
+ }
+
+ if (entry->album_url) {
+ grl_media_set_site (media, entry->album_url);
+ }
+
+ if (entry->album_image) {
+ grl_media_set_thumbnail (media, entry->album_image);
+ }
+
+ if (entry->album_duration) {
+ grl_media_set_duration (media, atoi (entry->album_duration));
+ }
+
+ /* Fields for track */
+ } else if (entry->category == JAMENDO_TRACK_CAT) {
+ if (entry->track_name) {
+ grl_media_set_title (media, entry->track_name);
+ }
+
+ if (entry->album_genre) {
+ grl_media_audio_set_genre (GRL_MEDIA_AUDIO (media),
+ entry->album_genre);
+ }
+
+ if (entry->track_url) {
+ grl_media_set_site (media, entry->track_url);
+ }
+
+ if (entry->album_image) {
+ grl_media_set_thumbnail (media, entry->album_image);
+ }
+
+ if (entry->track_stream) {
+ grl_media_set_url (media, entry->track_stream);
+ }
+
+ if (entry->track_duration) {
+ grl_media_set_duration (media, atoi (entry->track_duration));
+ }
+ } else if (entry->category == JAMENDO_FEEDS_CAT) {
+ if (entry->feed_name) {
+ grl_media_set_title (media, entry->feed_name);
+ }
+ }
+}
+
+static gboolean
+xml_parse_entries_idle (gpointer user_data)
+{
+ XmlParseEntries *xpe = (XmlParseEntries *) user_data;
+ gboolean parse_more;
+ GrlMedia *media = NULL;
+ Entry *entry;
+ gint remaining = 0;
+
+ GRL_DEBUG ("xml_parse_entries_idle");
+
+ parse_more = (xpe->cancelled == FALSE && xpe->node);
+
+ if (parse_more) {
+ entry = xml_parse_entry (xpe->doc, xpe->node);
+ if (entry->category == JAMENDO_TRACK_CAT) {
+ media = grl_media_audio_new ();
+ } else {
+ media = grl_media_box_new ();
+ }
+
+ update_media_from_entry (media, entry);
+ free_entry (entry);
+
+ xpe->index++;
+ xpe->node = xpe->node->next;
+ remaining = xpe->total_results - xpe->index;
+ }
+
+ if (parse_more || xpe->cancelled) {
+ switch (xpe->type) {
+ case BROWSE:
+ xpe->spec.bs->callback (xpe->spec.bs->source,
+ xpe->spec.bs->browse_id,
+ media,
+ remaining,
+ xpe->spec.bs->user_data,
+ NULL);
+ break;
+ case QUERY:
+ xpe->spec.qs->callback (xpe->spec.qs->source,
+ xpe->spec.qs->query_id,
+ media,
+ remaining,
+ xpe->spec.qs->user_data,
+ NULL);
+ break;
+ case SEARCH:
+ xpe->spec.ss->callback (xpe->spec.ss->source,
+ xpe->spec.ss->search_id,
+ media,
+ remaining,
+ xpe->spec.ss->user_data,
+ NULL);
+ break;
+ }
+ }
+
+ if (!parse_more) {
+ xmlFreeDoc (xpe->doc);
+ g_slice_free (XmlParseEntries, xpe);
+ }
+
+ return parse_more;
+}
+
+static void
+read_done_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ XmlParseEntries *xpe = (XmlParseEntries *) user_data;
+ gint error_code = -1;
+ GError *wc_error = NULL;
+ GError *error = NULL;
+ gchar *content = NULL;
+ Entry *entry = NULL;
+
+ /* Check if operation was cancelled */
+ if (xpe->cancelled) {
+ goto invoke_cb;
+ }
+
+ if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res,
+ &content,
+ NULL,
+ &wc_error)) {
+ switch (xpe->type) {
+ case METADATA:
+ error_code = GRL_CORE_ERROR_METADATA_FAILED;
+ break;
+ case BROWSE:
+ error_code = GRL_CORE_ERROR_BROWSE_FAILED;
+ break;
+ case QUERY:
+ error_code = GRL_CORE_ERROR_QUERY_FAILED;
+ break;
+ case SEARCH:
+ error_code = GRL_CORE_ERROR_SEARCH_FAILED;
+ break;
+ }
+
+ error = g_error_new (GRL_CORE_ERROR,
+ error_code,
+ "Failed to connect Jamendo: '%s'",
+ wc_error->message);
+ g_error_free (wc_error);
+ goto invoke_cb;
+ }
+
+ if (content) {
+ xml_parse_result (content, &error, xpe);
+ } else {
+ goto invoke_cb;
+ }
+
+ if (error) {
+ goto invoke_cb;
+ }
+
+ if (xpe->node) {
+ if (xpe->type == METADATA) {
+ entry = xml_parse_entry (xpe->doc, xpe->node);
+ xmlFreeDoc (xpe->doc);
+ update_media_from_entry (xpe->spec.ms->media, entry);
+ free_entry (entry);
+ goto invoke_cb;
+ } else {
+ g_idle_add (xml_parse_entries_idle, xpe);
+ }
+ } else {
+ if (xpe->type == METADATA) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Unable to get information: '%s'",
+ grl_media_get_id (xpe->spec.ms->media));
+ }
+ goto invoke_cb;
+ }
+
+ return;
+
+ invoke_cb:
+ switch (xpe->type) {
+ case METADATA:
+ xpe->spec.ms->callback (xpe->spec.ms->source,
+ xpe->spec.ms->media,
+ xpe->spec.ms->user_data,
+ error);
+ break;
+ case BROWSE:
+ xpe->spec.bs->callback (xpe->spec.bs->source,
+ xpe->spec.bs->browse_id,
+ NULL,
+ 0,
+ xpe->spec.bs->user_data,
+ error);
+ break;
+ case QUERY:
+ xpe->spec.qs->callback (xpe->spec.qs->source,
+ xpe->spec.qs->query_id,
+ NULL,
+ 0,
+ xpe->spec.qs->user_data,
+ error);
+ break;
+ case SEARCH:
+ xpe->spec.ss->callback (xpe->spec.ss->source,
+ xpe->spec.ss->search_id,
+ NULL,
+ 0,
+ xpe->spec.ss->user_data,
+ error);
+ break;
+ }
+
+ g_slice_free (XmlParseEntries, xpe);
+ if (error) {
+ g_error_free (error);
+ }
+}
+
+static void
+read_url_async (GrlJamendoSource *source,
+ const gchar *url,
+ gpointer user_data)
+{
+ if (!source->priv->wc)
+ source->priv->wc = g_object_new (GRL_TYPE_NET_WC, "throttling", 1, NULL);
+
+ source->priv->cancellable = g_cancellable_new ();
+
+ GRL_DEBUG ("Opening '%s'", url);
+ grl_net_wc_request_async (source->priv->wc,
+ url,
+ source->priv->cancellable,
+ read_done_cb,
+ user_data);
+}
+
+static void
+update_media_from_root (GrlMedia *media)
+{
+ grl_media_set_title (media, JAMENDO_ROOT_NAME);
+ grl_media_box_set_childcount (GRL_MEDIA_BOX (media), 3);
+}
+
+static void
+update_media_from_artists (GrlMedia *media)
+{
+ Entry entry = {
+ .category = JAMENDO_ARTIST_CAT,
+ .artist_name = JAMENDO_ARTIST "s",
+ };
+
+ update_media_from_entry (media, &entry);
+}
+
+static void
+update_media_from_albums (GrlMedia *media)
+{
+ Entry entry = {
+ .category = JAMENDO_ALBUM_CAT,
+ .album_name = JAMENDO_ALBUM "s",
+ };
+
+ update_media_from_entry (media, &entry);
+}
+
+static void
+update_media_from_feeds (GrlMedia *media)
+{
+ Entry entry = {
+ .category = JAMENDO_FEEDS_CAT,
+ .feed_name = "feeds",
+ };
+
+ update_media_from_entry (media, &entry);
+ grl_media_box_set_childcount (GRL_MEDIA_BOX (media), G_N_ELEMENTS(feeds));
+}
+
+static void
+send_toplevel_categories (GrlMediaSourceBrowseSpec *bs)
+{
+ GrlMedia *media;
+ gint remaining;
+
+ /* Check if all elements must be skipped */
+ if (bs->skip > 1 || bs->count == 0) {
+ bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, NULL);
+ return;
+ }
+
+ remaining = bs->count;
+
+ if (bs->skip == 0) {
+ media = grl_media_box_new ();
+ update_media_from_artists (media);
+ remaining--;
+ bs->callback (bs->source, bs->browse_id, media, remaining, bs->user_data, NULL);
+ }
+
+ if (remaining) {
+ media = grl_media_box_new ();
+ update_media_from_albums (media);
+ bs->callback (bs->source, bs->browse_id, media, remaining, bs->user_data, NULL);
+ remaining--;
+ }
+
+ if (remaining) {
+ media = grl_media_box_new ();
+ update_media_from_feeds (media);
+ bs->callback (bs->source, bs->browse_id, media, 0, bs->user_data, NULL);
+ }
+}
+
+static void
+update_media_from_feed (GrlMedia *media, int i)
+{
+ char *id;
+
+ id = g_strdup_printf("%d/%d", JAMENDO_FEEDS_CAT, i);
+ grl_media_set_id (media, id);
+ g_free (id);
+
+ grl_media_set_title (media, feeds[i].name);
+}
+
+static void
+send_feeds (GrlMediaSourceBrowseSpec *bs)
+{
+ int i;
+ int remaining;
+
+ remaining = MIN (bs->count, G_N_ELEMENTS (feeds));
+ for (i = bs->skip; remaining > 0 && i < G_N_ELEMENTS (feeds); i++) {
+ GrlMedia *media;
+
+ media = grl_media_box_new ();
+ update_media_from_feed (media, i);
+ remaining--;
+ bs->callback (bs->source,
+ bs->browse_id,
+ media,
+ remaining,
+ bs->user_data,
+ NULL);
+ }
+}
+
+static gchar *
+get_jamendo_keys (JamendoCategory category)
+{
+ gchar *jamendo_keys = NULL;
+ gchar *keys_for_artist = "artist_name+artist_genre+artist_image+artist_url";
+ gchar *keys_for_album = "album_name+album_genre+album_image+album_url+album_duration";
+ gchar *keys_for_track = "track_name+track_stream+track_url+track_duration";
+
+ if (category == JAMENDO_ARTIST_CAT) {
+ jamendo_keys = g_strconcat ("id+", keys_for_artist, NULL);
+ } else if (category == JAMENDO_ALBUM_CAT) {
+ jamendo_keys = g_strconcat ("id+", keys_for_artist,
+ "+", keys_for_album,
+ NULL);
+ } else if (category == JAMENDO_TRACK_CAT) {
+ jamendo_keys = g_strconcat ("id+", keys_for_artist,
+ "+", keys_for_album,
+ "+", keys_for_track,
+ NULL);
+ }
+
+ return jamendo_keys;
+}
+
+static gboolean
+parse_query (const gchar *query, JamendoCategory *category, gchar **term)
+{
+ if (!query) {
+ return FALSE;
+ }
+
+ if (g_str_has_prefix (query, JAMENDO_ARTIST "=")) {
+ *category = JAMENDO_ARTIST_CAT;
+ query += 7;
+ } else if (g_str_has_prefix (query, JAMENDO_ALBUM "=")) {
+ *category = JAMENDO_ALBUM_CAT;
+ query += 6;
+ } else if (g_str_has_prefix (query, JAMENDO_TRACK "=")) {
+ *category = JAMENDO_TRACK_CAT;
+ query += 6;
+ } else {
+ return FALSE;
+ }
+
+ *term = g_uri_escape_string (query, NULL, TRUE);
+ return TRUE;
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_jamendo_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_ARTIST,
+ GRL_METADATA_KEY_ALBUM,
+ GRL_METADATA_KEY_GENRE,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_DURATION,
+ GRL_METADATA_KEY_THUMBNAIL,
+ GRL_METADATA_KEY_SITE,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_jamendo_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ gchar *url = NULL;
+ gchar *jamendo_keys = NULL;
+ const gchar *id;
+ gchar **id_split = NULL;
+ XmlParseEntries *xpe = NULL;
+ GError *error = NULL;
+ JamendoCategory category;
+
+ GRL_DEBUG ("grl_jamendo_source_metadata");
+
+ if (!ms->media ||
+ !grl_data_key_is_known (GRL_DATA (ms->media),
+ GRL_METADATA_KEY_ID)) {
+ /* Get info from root */
+ if (!ms->media) {
+ ms->media = grl_media_box_new ();
+ }
+ update_media_from_root (ms->media);
+ } else {
+ id = grl_media_get_id (ms->media);
+ id_split = g_strsplit (id, JAMENDO_ID_SEP, 0);
+
+ if (g_strv_length (id_split) == 0) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Invalid id: '%s'",
+ id);
+ goto send_error;
+ }
+
+ category = atoi (id_split[0]);
+
+ if (category == JAMENDO_ARTIST_CAT) {
+ if (id_split[1]) {
+ /* Requesting information from a specific artist */
+ jamendo_keys = get_jamendo_keys (JAMENDO_ARTIST_CAT);
+ url =
+ g_strdup_printf (JAMENDO_GET_ARTIST,
+ jamendo_keys,
+ id_split[1]);
+ g_free (jamendo_keys);
+ } else {
+ /* Requesting information from artist category */
+ update_media_from_artists (ms->media);
+ }
+ } else if (category == JAMENDO_ALBUM_CAT) {
+ if (id_split[1]) {
+ /* Requesting information from a specific album */
+ jamendo_keys = get_jamendo_keys (JAMENDO_ALBUM_CAT);
+ url =
+ g_strdup_printf (JAMENDO_GET_ALBUM,
+ jamendo_keys,
+ id_split[1]);
+ g_free (jamendo_keys);
+ } else {
+ /* Requesting information from album category */
+ update_media_from_albums (ms->media);
+ }
+ } else if (category == JAMENDO_TRACK_CAT) {
+ if (id_split[1]) {
+ /* Requesting information from a specific song */
+ jamendo_keys = get_jamendo_keys (JAMENDO_TRACK_CAT);
+ url =
+ g_strdup_printf (JAMENDO_GET_TRACK,
+ jamendo_keys,
+ id_split[1]);
+ g_free (jamendo_keys);
+ } else {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Invalid id: '%s'",
+ id);
+ g_strfreev (id_split);
+ goto send_error;
+ }
+ } else if (category == JAMENDO_FEEDS_CAT) {
+ if (id_split[1]) {
+ int i;
+
+ i = atoi (id_split[0]);
+ update_media_from_feed (ms->media, i);
+ } else {
+ update_media_from_feeds (ms->media);
+ }
+ } else {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Invalid id: '%s'",
+ id);
+ g_strfreev (id_split);
+ goto send_error;
+ }
+ }
+
+ if (id_split) {
+ g_strfreev (id_split);
+ }
+
+ if (url) {
+ xpe = g_slice_new0 (XmlParseEntries);
+ xpe->type = METADATA;
+ xpe->spec.ms = ms;
+ read_url_async (GRL_JAMENDO_SOURCE (source), url, xpe);
+ g_free (url);
+ } else {
+ if (ms->media) {
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ }
+ }
+
+ return;
+
+ send_error:
+ ms->callback (ms->source, NULL, ms->user_data, error);
+ g_error_free (error);
+}
+
+static void
+grl_jamendo_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ gchar *url = NULL;
+ gchar *jamendo_keys;
+ gchar **container_split = NULL;
+ JamendoCategory category;
+ XmlParseEntries *xpe = NULL;
+ const gchar *container_id;
+ GError *error = NULL;
+ guint page_size;
+ guint page_number;
+ guint page_offset;
+
+ GRL_DEBUG ("grl_jamendo_source_browse");
+
+ container_id = grl_media_get_id (bs->container);
+
+ if (!container_id) {
+ /* Root category: return top-level predefined categories */
+ send_toplevel_categories (bs);
+ return;
+ }
+
+ container_split = g_strsplit (container_id, JAMENDO_ID_SEP, 0);
+
+ if (g_strv_length (container_split) == 0) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Invalid container-id: '%s'",
+ container_id);
+ } else {
+ category = atoi (container_split[0]);
+ grl_paging_translate (bs->skip,
+ bs->count,
+ 0,
+ &page_size,
+ &page_number,
+ &page_offset);
+
+ if (category == JAMENDO_ARTIST_CAT) {
+ if (container_split[1]) {
+ jamendo_keys = get_jamendo_keys (JAMENDO_ALBUM_CAT);
+ /* Requesting information from a specific artist */
+ url =
+ g_strdup_printf (JAMENDO_GET_ALBUMS_FROM_ARTIST,
+ jamendo_keys,
+ page_size,
+ page_number,
+ container_split[1]);
+ } else {
+ /* Browsing through artists */
+ jamendo_keys = get_jamendo_keys (JAMENDO_ARTIST_CAT);
+ url = g_strdup_printf (JAMENDO_GET_ARTISTS,
+ jamendo_keys,
+ page_size,
+ page_number);
+ }
+ g_free (jamendo_keys);
+
+ } else if (category == JAMENDO_ALBUM_CAT) {
+ if (container_split[1]) {
+ /* Requesting information from a specific album */
+ jamendo_keys = get_jamendo_keys (JAMENDO_TRACK_CAT);
+ url =
+ g_strdup_printf (JAMENDO_GET_TRACKS_FROM_ALBUM,
+ jamendo_keys,
+ page_size,
+ page_number,
+ container_split[1]);
+ } else {
+ /* Browsing through albums */
+ jamendo_keys = get_jamendo_keys (JAMENDO_ALBUM_CAT);
+ url = g_strdup_printf (JAMENDO_GET_ALBUMS,
+ jamendo_keys,
+ page_size,
+ page_number);
+ }
+ g_free (jamendo_keys);
+
+ } else if (category == JAMENDO_FEEDS_CAT) {
+ if (container_split[1]) {
+ int feed_id;
+
+ feed_id = atoi (container_split[1]);
+ jamendo_keys = get_jamendo_keys (feeds[feed_id].cat);
+ url = g_strdup_printf (feeds[feed_id].url,
+ jamendo_keys,
+ page_size,
+ page_number);
+ } else {
+ send_feeds (bs);
+ return;
+ }
+
+ } else if (category == JAMENDO_TRACK_CAT) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Cannot browse through a track: '%s'",
+ container_id);
+ } else {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Invalid container-id: '%s'",
+ container_id);
+ }
+ }
+
+ if (error) {
+ bs->callback (source, bs->browse_id, NULL, 0, bs->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ xpe = g_slice_new0 (XmlParseEntries);
+ xpe->type = BROWSE;
+ xpe->offset = page_offset;
+ xpe->spec.bs = bs;
+
+ grl_media_source_set_operation_data (source, bs->browse_id, xpe);
+
+ read_url_async (GRL_JAMENDO_SOURCE (source), url, xpe);
+ g_free (url);
+ if (container_split) {
+ g_strfreev (container_split);
+ }
+}
+
+/*
+ * Query format is "<type>=<text>", where <type> can be either 'artist', 'album'
+ * or 'track' and 'text' is the term to search.
+ *
+ * The result will be also a <type>.
+ *
+ * Example: search for artists that have the "Shake" in their name or
+ * description: "artist=Shake"
+ *
+ */
+static void
+grl_jamendo_source_query (GrlMediaSource *source,
+ GrlMediaSourceQuerySpec *qs)
+{
+ GError *error = NULL;
+ JamendoCategory category;
+ gchar *term = NULL;
+ gchar *url;
+ gchar *jamendo_keys = NULL;
+ gchar *query = NULL;
+ XmlParseEntries *xpe = NULL;
+ guint page_size;
+ guint page_number;
+ guint page_offset;
+
+ GRL_DEBUG ("grl_jamendo_source_query");
+
+ if (!parse_query (qs->query, &category, &term)) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_QUERY_FAILED,
+ "Query malformed: '%s'",
+ qs->query);
+ goto send_error;
+ }
+
+ jamendo_keys = get_jamendo_keys (category);
+ switch (category) {
+ case JAMENDO_ARTIST_CAT:
+ query = JAMENDO_SEARCH_ARTIST;
+ break;
+ case JAMENDO_ALBUM_CAT:
+ query = JAMENDO_SEARCH_ALBUM;
+ break;
+ case JAMENDO_TRACK_CAT:
+ query = JAMENDO_SEARCH_TRACK;
+ break;
+ default:
+ g_return_if_reached ();
+ }
+
+ grl_paging_translate (qs->skip,
+ qs->count,
+ 0,
+ &page_size,
+ &page_number,
+ &page_offset);
+
+ url = g_strdup_printf (query,
+ jamendo_keys,
+ page_size,
+ page_number,
+ term);
+ g_free (term);
+
+ xpe = g_slice_new0 (XmlParseEntries);
+ xpe->type = QUERY;
+ xpe->offset = page_offset;
+ xpe->spec.qs = qs;
+
+ grl_media_source_set_operation_data (source, qs->query_id, xpe);
+
+ read_url_async (GRL_JAMENDO_SOURCE (source), url, xpe);
+ g_free (url);
+
+ return;
+
+ send_error:
+ qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
+ g_error_free (error);
+}
+
+
+static void
+grl_jamendo_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss)
+{
+ XmlParseEntries *xpe;
+ gchar *jamendo_keys;
+ gchar *url;
+ guint page_size;
+ guint page_number;
+ guint page_offset;
+
+ GRL_DEBUG ("grl_jamendo_source_search");
+
+ jamendo_keys = get_jamendo_keys (JAMENDO_TRACK_CAT);
+
+ grl_paging_translate (ss->skip,
+ ss->count,
+ 0,
+ &page_size,
+ &page_number,
+ &page_offset);
+
+ if (ss->text) {
+ url = g_strdup_printf (JAMENDO_SEARCH_TRACK,
+ jamendo_keys,
+ page_size,
+ page_number,
+ ss->text);
+ } else {
+ url = g_strdup_printf (JAMENDO_SEARCH_ALL,
+ jamendo_keys,
+ page_size,
+ page_number);
+ }
+
+ xpe = g_slice_new0 (XmlParseEntries);
+ xpe->type = SEARCH;
+ xpe->offset = page_offset;
+ xpe->spec.ss = ss;
+
+ grl_media_source_set_operation_data (source, ss->search_id, xpe);
+
+ read_url_async (GRL_JAMENDO_SOURCE (source), url, xpe);
+ g_free (url);
+}
+
+static void
+grl_jamendo_source_cancel (GrlMediaSource *source, guint operation_id)
+{
+ XmlParseEntries *xpe;
+ GrlJamendoSourcePriv *priv;
+
+ g_return_if_fail (GRL_IS_JAMENDO_SOURCE (source));
+
+ priv = GRL_JAMENDO_SOURCE_GET_PRIVATE (source);
+
+ if (priv->cancellable && G_IS_CANCELLABLE (priv->cancellable))
+ g_cancellable_cancel (priv->cancellable);
+ priv->cancellable = NULL;
+
+ if (priv->wc)
+ grl_net_wc_flush_delayed_requests (priv->wc);
+
+ GRL_DEBUG ("grl_jamendo_source_cancel");
+
+ xpe = (XmlParseEntries *) grl_media_source_get_operation_data (source,
+ operation_id);
+
+ if (xpe) {
+ xpe->cancelled = TRUE;
+ }
+}
diff --git a/src/media/jamendo/grl-jamendo.h b/src/media/jamendo/grl-jamendo.h
new file mode 100644
index 0000000..15a4b75
--- /dev/null
+++ b/src/media/jamendo/grl-jamendo.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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_JAMENDO_SOURCE_H_
+#define _GRL_JAMENDO_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_JAMENDO_SOURCE_TYPE \
+ (grl_jamendo_source_get_type ())
+
+#define GRL_JAMENDO_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_JAMENDO_SOURCE_TYPE, \
+ GrlJamendoSource))
+
+#define GRL_IS_JAMENDO_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_JAMENDO_SOURCE_TYPE))
+
+#define GRL_JAMENDO_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_JAMENDO_SOURCE_TYPE, \
+ GrlJamendoSourceClass))
+
+#define GRL_IS_JAMENDO_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_JAMENDO_SOURCE_TYPE))
+
+#define GRL_JAMENDO_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_JAMENDO_SOURCE_TYPE, \
+ GrlJamendoSourceClass))
+
+typedef struct _GrlJamendoSource GrlJamendoSource;
+typedef struct _GrlJamendoSourcePriv GrlJamendoSourcePriv;
+
+struct _GrlJamendoSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlJamendoSourcePriv *priv;
+
+};
+
+typedef struct _GrlJamendoSourceClass GrlJamendoSourceClass;
+
+struct _GrlJamendoSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_jamendo_source_get_type (void);
+
+#endif /* _GRL_JAMENDO_SOURCE_H_ */
diff --git a/src/media/jamendo/grl-jamendo.xml b/src/media/jamendo/grl-jamendo.xml
new file mode 100644
index 0000000..e864cfe
--- /dev/null
+++ b/src/media/jamendo/grl-jamendo.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Jamendo</name>
+ <description>A plugin for browsing and searching Jamendo videos</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/podcasts/Makefile.am b/src/media/podcasts/Makefile.am
new file mode 100644
index 0000000..2383042
--- /dev/null
+++ b/src/media/podcasts/Makefile.am
@@ -0,0 +1,38 @@
+#
+# Makefile.am
+#
+# Author: Iago Toral Quiroga <itoral igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlpodcasts.la
+
+libgrlpodcasts_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GRLNET_CFLAGS) \
+ $(XML_CFLAGS) \
+ $(SQLITE_CFLAGS)
+
+libgrlpodcasts_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GRLNET_LIBS) \
+ $(XML_LIBS) \
+ $(SQLITE_LIBS)
+
+libgrlpodcasts_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlpodcasts_la_SOURCES = grl-podcasts.c grl-podcasts.h
+
+libdir = $(GRL_PLUGINS_DIR)
+podcastsxmldir = $(GRL_PLUGINS_CONF_DIR)
+podcastsxml_DATA = $(PODCASTS_PLUGIN_ID).xml
+
+EXTRA_DIST = $(podcastsxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/podcasts/TODO b/src/media/podcasts/TODO
new file mode 100644
index 0000000..12f3311
--- /dev/null
+++ b/src/media/podcasts/TODO
@@ -0,0 +1,5 @@
+- IGN podcasts contains items with no enclosure. These should be skipped.
+- Support more tags (like author, site, etc)
+- Parsing and inserting in the DB seems to block long enough, split the parsing to handle only
+ one element in each go.
+- Implement support for remove() -- Needs support in core first
diff --git a/src/media/podcasts/grl-podcasts.c b/src/media/podcasts/grl-podcasts.c
new file mode 100644
index 0000000..a4d5ad3
--- /dev/null
+++ b/src/media/podcasts/grl-podcasts.c
@@ -0,0 +1,1636 @@
+/*
+ * Copyright (C) 2010, 2011 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
+#include <net/grl-net.h>
+#include <libxml/xpath.h>
+#include <sqlite3.h>
+#include <string.h>
+
+#include "grl-podcasts.h"
+
+#define GRL_PODCASTS_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_PODCASTS_SOURCE_TYPE, \
+ GrlPodcastsPrivate))
+
+#define GRL_ROOT_TITLE "Podcasts"
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT podcasts_log_domain
+GRL_LOG_DOMAIN_STATIC(podcasts_log_domain);
+
+/* --- Database --- */
+
+#define GRL_SQL_DB ".grl-podcasts"
+
+#define GRL_SQL_CREATE_TABLE_PODCASTS \
+ "CREATE TABLE IF NOT EXISTS podcasts (" \
+ "id INTEGER PRIMARY KEY AUTOINCREMENT," \
+ "title TEXT," \
+ "url TEXT," \
+ "desc TEXT," \
+ "last_refreshed DATE)"
+
+#define GRL_SQL_CREATE_TABLE_STREAMS \
+ "CREATE TABLE IF NOT EXISTS streams ( " \
+ "podcast INTEGER REFERENCES podcasts (id), " \
+ "url TEXT, " \
+ "title TEXT, " \
+ "length INTEGER, " \
+ "mime TEXT, " \
+ "date TEXT, " \
+ "desc TEXT)"
+
+#define GRL_SQL_GET_PODCASTS \
+ "SELECT p.*, count(s.podcast <> '') " \
+ "FROM podcasts p LEFT OUTER JOIN streams s " \
+ " ON p.id = s.podcast " \
+ "GROUP BY p.id " \
+ "LIMIT %u OFFSET %u"
+
+#define GRL_SQL_GET_PODCASTS_BY_QUERY \
+ "SELECT p.*, count(s.podcast <> '') " \
+ "FROM podcasts p LEFT OUTER JOIN streams s " \
+ " ON p.id = s.podcast " \
+ "WHERE %s " \
+ "GROUP BY p.id " \
+ "LIMIT %u OFFSET %u"
+
+#define GRL_SQL_GET_PODCAST_BY_ID \
+ "SELECT * FROM podcasts " \
+ "WHERE id='%s' " \
+ "LIMIT 1"
+
+#define GRL_SQL_STORE_PODCAST \
+ "INSERT INTO podcasts " \
+ "(url, title, desc) " \
+ "VALUES (?, ?, ?)"
+
+#define GRL_SQL_REMOVE_PODCAST \
+ "DELETE FROM podcasts " \
+ "WHERE id='%s'"
+
+#define GRL_SQL_REMOVE_STREAM \
+ "DELETE FROM streams " \
+ "WHERE url='%s'"
+
+#define GRL_SQL_STORE_STREAM \
+ "INSERT INTO streams " \
+ "(podcast, url, title, length, mime, date, desc) " \
+ "VALUES (?, ?, ?, ?, ?, ?, ?)"
+
+#define GRL_SQL_DELETE_PODCAST_STREAMS \
+ "DELETE FROM streams WHERE podcast='%s'"
+
+#define GRL_SQL_GET_PODCAST_STREAMS \
+ "SELECT * FROM streams " \
+ "WHERE podcast='%s' " \
+ "LIMIT %u OFFSET %u"
+
+#define GRL_SQL_GET_PODCAST_STREAMS_BY_TEXT \
+ "SELECT s.* " \
+ "FROM streams s LEFT OUTER JOIN podcasts p " \
+ " ON s.podcast = p.id " \
+ "WHERE s.title LIKE '%%%s%%' OR s.desc LIKE '%%%s%%' " \
+ " OR p.title LIKE '%%%s%%' OR p.desc LIKE '%%%s%%' " \
+ "LIMIT %u OFFSET %u"
+
+#define GRL_SQL_GET_PODCAST_STREAMS_ALL \
+ "SELECT * FROM streams " \
+ "LIMIT %u OFFSET %u"
+
+#define GRL_SQL_GET_PODCAST_STREAM \
+ "SELECT * FROM streams " \
+ "WHERE url='%s' " \
+ "LIMIT 1"
+
+#define GRL_SQL_TOUCH_PODCAST \
+ "UPDATE podcasts " \
+ "SET last_refreshed='%s' " \
+ "WHERE id='%s'"
+
+/* --- Other --- */
+
+#define CACHE_DURATION (24 * 60 * 60)
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID PODCASTS_PLUGIN_ID
+
+#define SOURCE_ID "grl-podcasts"
+#define SOURCE_NAME "Podcasts"
+#define SOURCE_DESC "A source for browsing podcasts"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+enum {
+ PODCAST_ID = 0,
+ PODCAST_TITLE,
+ PODCAST_URL,
+ PODCAST_DESC,
+ PODCAST_LAST_REFRESHED,
+ PODCAST_LAST,
+};
+
+enum {
+ STREAM_PODCAST = 0,
+ STREAM_URL,
+ STREAM_TITLE,
+ STREAM_LENGTH,
+ STREAM_MIME,
+ STREAM_DATE,
+ STREAM_DESC,
+};
+
+typedef void (*AsyncReadCbFunc) (gchar *data, gpointer user_data);
+
+typedef struct {
+ AsyncReadCbFunc callback;
+ gchar *url;
+ gpointer user_data;
+} AsyncReadCb;
+
+typedef struct {
+ gchar *id;
+ gchar *url;
+ gchar *title;
+ gchar *published;
+ gchar *duration;
+ gchar *summary;
+ gchar *mime;
+} Entry;
+
+struct _GrlPodcastsPrivate {
+ sqlite3 *db;
+ GrlNetWc *wc;
+ gboolean notify_changes;
+};
+
+typedef struct {
+ GrlMediaSource *source;
+ guint operation_id;
+ const gchar *media_id;
+ guint skip;
+ guint count;
+ const gchar *text;
+ GrlMediaSourceResultCb callback;
+ guint error_code;
+ gboolean is_query;
+ gpointer user_data;
+} OperationSpec;
+
+typedef struct {
+ OperationSpec *os;
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpathCtx;
+ xmlXPathObjectPtr xpathObj;
+ guint parse_count;
+ guint parse_index;
+ guint parse_valid_index;
+ GrlMedia *last_media;
+} OperationSpecParse;
+
+static GrlPodcastsSource *grl_podcasts_source_new (void);
+
+static void grl_podcasts_source_finalize (GObject *plugin);
+
+static const GList *grl_podcasts_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_podcasts_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+static void grl_podcasts_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+static void grl_podcasts_source_query (GrlMediaSource *source,
+ GrlMediaSourceQuerySpec *qs);
+static void grl_podcasts_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms);
+static void grl_podcasts_source_store (GrlMediaSource *source,
+ GrlMediaSourceStoreSpec *ss);
+static void grl_podcasts_source_remove (GrlMediaSource *source,
+ GrlMediaSourceRemoveSpec *rs);
+static gboolean grl_podcasts_source_notify_change_start (GrlMediaSource *source,
+ GError **error);
+static gboolean grl_podcasts_source_notify_change_stop (GrlMediaSource *source,
+ GError **error);
+
+/* =================== Podcasts Plugin =============== */
+
+static gboolean
+grl_podcasts_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (podcasts_log_domain, "podcasts");
+
+ GRL_DEBUG ("podcasts_plugin_init");
+
+ GrlPodcastsSource *source = grl_podcasts_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_podcasts_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Podcasts GObject ================ */
+
+static GrlPodcastsSource *
+grl_podcasts_source_new (void)
+{
+ GRL_DEBUG ("grl_podcasts_source_new");
+ return g_object_new (GRL_PODCASTS_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_podcasts_source_class_init (GrlPodcastsSourceClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+
+ gobject_class->finalize = grl_podcasts_source_finalize;
+
+ source_class->browse = grl_podcasts_source_browse;
+ source_class->search = grl_podcasts_source_search;
+ source_class->query = grl_podcasts_source_query;
+ source_class->metadata = grl_podcasts_source_metadata;
+ source_class->store = grl_podcasts_source_store;
+ source_class->remove = grl_podcasts_source_remove;
+ source_class->notify_change_start = grl_podcasts_source_notify_change_start;
+ source_class->notify_change_stop = grl_podcasts_source_notify_change_stop;
+
+ metadata_class->supported_keys = grl_podcasts_source_supported_keys;
+
+ g_type_class_add_private (klass, sizeof (GrlPodcastsPrivate));
+}
+
+static void
+grl_podcasts_source_init (GrlPodcastsSource *source)
+{
+ gint r;
+ const gchar *home;
+ gchar *db_path;
+ gchar *sql_error = NULL;
+
+ source->priv = GRL_PODCASTS_GET_PRIVATE (source);
+
+ home = g_getenv ("HOME");
+ if (!home) {
+ GRL_WARNING ("$HOME not set, cannot open database");
+ return;
+ }
+
+ GRL_DEBUG ("Opening database connection...");
+ db_path = g_strconcat (home, G_DIR_SEPARATOR_S, GRL_SQL_DB, NULL);
+ r = sqlite3_open (db_path, &source->priv->db);
+ if (r) {
+ g_critical ("Failed to open database '%s': %s",
+ db_path, sqlite3_errmsg (source->priv->db));
+ sqlite3_close (source->priv->db);
+ return;
+ }
+ GRL_DEBUG (" OK");
+
+ GRL_DEBUG ("Checking database tables...");
+ r = sqlite3_exec (source->priv->db, GRL_SQL_CREATE_TABLE_PODCASTS,
+ NULL, NULL, &sql_error);
+
+ if (!r) {
+ /* TODO: if this fails, sqlite stays in an unreliable state fix that */
+ r = sqlite3_exec (source->priv->db, GRL_SQL_CREATE_TABLE_STREAMS,
+ NULL, NULL, &sql_error);
+ }
+ if (r) {
+ if (sql_error) {
+ GRL_WARNING ("Failed to create database tables: %s", sql_error);
+ sqlite3_free (sql_error);
+ sql_error = NULL;
+ } else {
+ GRL_WARNING ("Failed to create database tables.");
+ }
+ sqlite3_close (source->priv->db);
+ return;
+ }
+ GRL_DEBUG (" OK");
+
+ g_free (db_path);
+}
+
+G_DEFINE_TYPE (GrlPodcastsSource, grl_podcasts_source, GRL_TYPE_MEDIA_SOURCE);
+
+static void
+grl_podcasts_source_finalize (GObject *object)
+{
+ GrlPodcastsSource *source;
+
+ GRL_DEBUG ("grl_podcasts_source_finalize");
+
+ source = GRL_PODCASTS_SOURCE (object);
+
+ if (source->priv->wc)
+ g_object_unref (source->priv->wc);
+
+ sqlite3_close (source->priv->db);
+
+ G_OBJECT_CLASS (grl_podcasts_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static void
+print_entry (Entry *entry)
+{
+ g_print ("Entry Information:\n");
+ g_print (" ID: %s\n", entry->id);
+ g_print (" Title: %s\n", entry->title);
+ g_print (" Date: %s\n", entry->published);
+ g_print (" Duration: %s\n", entry->duration);
+ g_print (" Summary: %s\n", entry->summary);
+ g_print (" URL: %s\n", entry->url);
+ g_print (" Mime: %s\n", entry->mime);
+}
+
+static void
+free_entry (Entry *entry)
+{
+ g_free (entry->id);
+ g_free (entry->title);
+ g_free (entry->published);
+ g_free (entry->summary);
+ g_free (entry->url);
+ g_free (entry->mime);
+}
+
+static void
+read_done_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ AsyncReadCb *arc = (AsyncReadCb *) user_data;
+ GError *wc_error = NULL;
+ gchar *content = NULL;
+
+ GRL_DEBUG (" Done");
+
+ grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res,
+ &content,
+ NULL,
+ &wc_error);
+ if (wc_error) {
+ GRL_WARNING ("Failed to open '%s': %s", arc->url, wc_error->message);
+ g_error_free (wc_error);
+ } else {
+ arc->callback (content, arc->user_data);
+ }
+ g_free (arc->url);
+ g_slice_free (AsyncReadCb, arc);
+}
+
+static void
+read_url_async (GrlPodcastsSource *source,
+ const gchar *url,
+ AsyncReadCbFunc callback,
+ gpointer user_data)
+{
+ AsyncReadCb *arc;
+
+ GRL_DEBUG ("Opening async '%s'", url);
+
+ arc = g_slice_new0 (AsyncReadCb);
+ arc->url = g_strdup (url);
+ arc->callback = callback;
+ arc->user_data = user_data;
+
+ /* We would need a different Wc if we change of URL.
+ * In this case, as we don't know the previous URL,
+ * we ditch the Wc and create another. It's cheap.
+ */
+ if (!source->priv->wc)
+ g_object_unref (source->priv->wc);
+
+ source->priv->wc = grl_net_wc_new ();
+ grl_net_wc_request_async (source->priv->wc, url, NULL, read_done_cb, arc);
+}
+
+static gint
+duration_to_seconds (const gchar *str)
+{
+ gint seconds = 0;
+ gchar **parts;
+ gint i;
+ guint multiplier = 1;
+
+ if (!str || str[0] == '\0') {
+ return 0;
+ }
+
+ parts = g_strsplit (str, ":", 3);
+
+ /* Get last portion (seconds) */
+ i = 0;
+ while (parts[i]) i++;
+ if (i == 0) {
+ g_strfreev (parts);
+ return 0;
+ } else {
+ i--;
+ }
+
+ do {
+ seconds += atoi (parts[i]) * multiplier;
+ multiplier *= 60;
+ i--;
+ } while (i >= 0);
+
+ g_strfreev (parts);
+
+ return seconds;
+}
+
+static gboolean
+mime_is_video (const gchar *mime)
+{
+ return mime && strstr (mime, "video") != NULL;
+}
+
+static gboolean
+mime_is_audio (const gchar *mime)
+{
+ return mime && strstr (mime, "audio") != NULL;
+}
+
+static gchar *
+get_site_from_url (const gchar *url)
+{
+ gchar *p;
+
+ if (g_str_has_prefix (url, "file://")) {
+ return NULL;
+ }
+
+ p = strstr (url, "://");
+ if (!p) {
+ return NULL;
+ } else {
+ p += 3;
+ }
+
+ while (*p != '/') p++;
+
+ return g_strndup (url, p - url);
+}
+
+static GrlMedia *
+build_media (GrlMedia *content,
+ gboolean is_podcast,
+ const gchar *id,
+ const gchar *title,
+ const gchar *url,
+ const gchar *desc,
+ const gchar *mime,
+ const gchar *date,
+ guint duration,
+ guint childcount)
+{
+ GrlMedia *media = NULL;
+ gchar *site;
+
+ if (content) {
+ media = content;
+ }
+
+ if (is_podcast) {
+ if (!media) {
+ media = GRL_MEDIA (grl_media_box_new ());
+ }
+
+ grl_media_set_id (media, id);
+ if (desc)
+ grl_media_set_description (media, desc);
+ grl_media_box_set_childcount (GRL_MEDIA_BOX (media), childcount);
+ } else {
+ if (!media) {
+ if (mime_is_audio (mime)) {
+ media = grl_media_audio_new ();
+ } else if (mime_is_video (mime)) {
+ media = grl_media_video_new ();
+ } else {
+ media = grl_media_new ();
+ }
+ }
+
+ grl_media_set_id (media, url);
+ if (date)
+ grl_media_set_date (media, date);
+ if (desc)
+ grl_media_set_description (media, desc);
+ if (mime)
+ grl_media_set_mime (media, mime);
+ if (duration > 0) {
+ grl_media_set_duration (media, duration);
+ }
+ }
+
+ grl_media_set_title (media, title);
+ grl_media_set_url (media, url);
+
+ site = get_site_from_url (url);
+ if (site) {
+ grl_media_set_site (media, site);
+ g_free (site);
+ }
+
+ return media;
+}
+
+static GrlMedia *
+build_media_from_entry (Entry *entry)
+{
+ GrlMedia *media;
+ gint duration;
+
+ duration = duration_to_seconds (entry->duration);
+ media = build_media (NULL, FALSE,
+ entry->url, entry->title, entry->url,
+ entry->summary, entry->mime, entry->published,
+ duration, 0);
+ return media;
+}
+
+static GrlMedia *
+build_media_from_stmt (GrlMedia *content,
+ sqlite3_stmt *sql_stmt,
+ gboolean is_podcast)
+{
+ GrlMedia *media;
+ gchar *id;
+ gchar *title;
+ gchar *url;
+ gchar *desc;
+ gchar *mime;
+ gchar *date;
+ guint duration;
+ guint childcount;
+
+ if (is_podcast) {
+ id = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_ID);
+ title = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_TITLE);
+ url = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_URL);
+ desc = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_DESC);
+ childcount = (guint) sqlite3_column_int (sql_stmt, PODCAST_LAST);
+ media = build_media (content, is_podcast,
+ id, title, url, desc, NULL, NULL, 0, childcount);
+ } else {
+ mime = (gchar *) sqlite3_column_text (sql_stmt, STREAM_MIME);
+ url = (gchar *) sqlite3_column_text (sql_stmt, STREAM_URL);
+ title = (gchar *) sqlite3_column_text (sql_stmt, STREAM_TITLE);
+ date = (gchar *) sqlite3_column_text (sql_stmt, STREAM_DATE);
+ desc = (gchar *) sqlite3_column_text (sql_stmt, STREAM_DESC);
+ duration = sqlite3_column_int (sql_stmt, STREAM_LENGTH);
+ media = build_media (content, is_podcast,
+ url, title, url, desc, mime, date, duration, 0);
+ }
+
+ return media;
+}
+
+static void
+produce_podcast_contents_from_db (OperationSpec *os)
+{
+ sqlite3 *db;
+ gchar *sql;
+ sqlite3_stmt *sql_stmt = NULL;
+ GList *iter, *medias = NULL;
+ guint count = 0;
+ GrlMedia *media;
+ gint r;
+ GError *error = NULL;
+
+ GRL_DEBUG ("produce_podcast_contents_from_db");
+
+ db = GRL_PODCASTS_SOURCE (os->source)->priv->db;
+ /* Check if searching or browsing */
+ if (os->is_query) {
+ if (os->text) {
+ /* Search text */
+ sql = g_strdup_printf (GRL_SQL_GET_PODCAST_STREAMS_BY_TEXT,
+ os->text, os->text, os->text, os->text,
+ os->count, os->skip);
+ } else {
+ /* Return all */
+ sql = g_strdup_printf (GRL_SQL_GET_PODCAST_STREAMS_ALL,
+ os->count, os->skip);
+ }
+ } else {
+ sql = g_strdup_printf (GRL_SQL_GET_PODCAST_STREAMS,
+ os->media_id, os->count, os->skip);
+ }
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to retrieve podcast streams: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to retrieve podcast streams");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ while (r == SQLITE_ROW) {
+ media = build_media_from_stmt (NULL, sql_stmt, FALSE);
+ medias = g_list_prepend (medias, media);
+ count++;
+ r = sqlite3_step (sql_stmt);
+ }
+
+ if (r != SQLITE_DONE) {
+ GRL_WARNING ("Failed to retrive podcast streams: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to retrieve podcast streams");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+ sqlite3_finalize (sql_stmt);
+ return;
+ }
+
+ sqlite3_finalize (sql_stmt);
+
+ if (count > 0) {
+ medias = g_list_reverse (medias);
+ iter = medias;
+ while (iter) {
+ media = GRL_MEDIA (iter->data);
+ os->callback (os->source,
+ os->operation_id,
+ media,
+ --count,
+ os->user_data,
+ NULL);
+ iter = g_list_next (iter);
+ }
+ g_list_free (medias);
+ } else {
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
+ }
+}
+
+static void
+remove_podcast_streams (sqlite3 *db, const gchar *podcast_id, GError **error)
+{
+ gchar *sql;
+ gchar *sql_error;
+ gint r;
+
+ sql = g_strdup_printf (GRL_SQL_DELETE_PODCAST_STREAMS, podcast_id);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_exec (db, sql, NULL, NULL, &sql_error);
+ g_free (sql);
+ if (r) {
+ GRL_WARNING ("Failed to remove podcast streams cache: %s", sql_error);
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_REMOVE_FAILED,
+ "Failed to remove podcast streams");
+ sqlite3_free (error);
+ }
+}
+
+static void
+remove_podcast (GrlPodcastsSource *podcasts_source,
+ const gchar *podcast_id,
+ GError **error)
+{
+ gint r;
+ gchar *sql_error;
+ gchar *sql;
+
+ GRL_DEBUG ("remove_podcast");
+
+ remove_podcast_streams (podcasts_source->priv->db, podcast_id, error);
+ if (*error) {
+ return;
+ }
+
+ sql = g_strdup_printf (GRL_SQL_REMOVE_PODCAST, podcast_id);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_exec (podcasts_source->priv->db, sql, NULL, NULL, &sql_error);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to remove podcast '%s': %s", podcast_id, sql_error);
+ g_set_error_literal (error,
+ GRL_CORE_ERROR,
+ GRL_CORE_ERROR_REMOVE_FAILED,
+ "Failed to remove podcast");
+ sqlite3_free (sql_error);
+ } else if (podcasts_source->priv->notify_changes) {
+ grl_media_source_notify_change (GRL_MEDIA_SOURCE (podcasts_source),
+ NULL,
+ GRL_CONTENT_REMOVED,
+ TRUE);
+ }
+}
+
+static void
+remove_stream (GrlPodcastsSource *podcasts_source,
+ const gchar *url,
+ GError **error)
+{
+ gint r;
+ gchar *sql_error;
+ gchar *sql;
+
+ GRL_DEBUG ("remove_stream");
+
+ sql = g_strdup_printf (GRL_SQL_REMOVE_STREAM, url);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_exec (podcasts_source->priv->db, sql, NULL, NULL, &sql_error);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to remove podcast stream '%s': %s", url, sql_error);
+ g_set_error_literal (error,
+ GRL_CORE_ERROR,
+ GRL_CORE_ERROR_REMOVE_FAILED,
+ "Failed to remove podcast stream");
+ sqlite3_free (sql_error);
+ } else if (podcasts_source->priv->notify_changes) {
+ grl_media_source_notify_change (GRL_MEDIA_SOURCE (podcasts_source),
+ NULL,
+ GRL_CONTENT_REMOVED,
+ TRUE);
+ }
+}
+
+static void
+store_podcast (GrlPodcastsSource *podcasts_source,
+ GrlMedia *podcast,
+ GError **error)
+{
+ gint r;
+ sqlite3_stmt *sql_stmt = NULL;
+ const gchar *title;
+ const gchar *url;
+ const gchar *desc;
+ gchar *id;
+
+ GRL_DEBUG ("store_podcast");
+
+ title = grl_media_get_title (podcast);
+ url = grl_media_get_url (podcast);
+ desc = grl_media_get_description (podcast);
+
+ GRL_DEBUG ("%s", GRL_SQL_STORE_PODCAST);
+ r = sqlite3_prepare_v2 (podcasts_source->priv->db,
+ GRL_SQL_STORE_PODCAST,
+ strlen (GRL_SQL_STORE_PODCAST),
+ &sql_stmt, NULL);
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to store podcast '%s': %s", title,
+ sqlite3_errmsg (podcasts_source->priv->db));
+ g_set_error (error,
+ GRL_CORE_ERROR,
+ GRL_CORE_ERROR_STORE_FAILED,
+ "Failed to store podcast '%s'", title);
+ return;
+ }
+
+ sqlite3_bind_text (sql_stmt, 1, url, -1, SQLITE_STATIC);
+ sqlite3_bind_text (sql_stmt, 2, title, -1, SQLITE_STATIC);
+ if (desc) {
+ sqlite3_bind_text (sql_stmt, 3, desc, -1, SQLITE_STATIC);
+ } else {
+ sqlite3_bind_text (sql_stmt, 3, "", -1, SQLITE_STATIC);
+ }
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ if (r != SQLITE_DONE) {
+ GRL_WARNING ("Failed to store podcast '%s': %s", title,
+ sqlite3_errmsg (podcasts_source->priv->db));
+ g_set_error (error,
+ GRL_CORE_ERROR,
+ GRL_CORE_ERROR_STORE_FAILED,
+ "Failed to store podcast '%s'", title);
+ sqlite3_finalize (sql_stmt);
+ return;
+ }
+
+ sqlite3_finalize (sql_stmt);
+
+ id = g_strdup_printf ("%llu",
+ sqlite3_last_insert_rowid (podcasts_source->priv->db));
+ grl_media_set_id (podcast, id);
+ g_free (id);
+
+ if (podcasts_source->priv->notify_changes) {
+ grl_media_source_notify_change (GRL_MEDIA_SOURCE (podcasts_source),
+ NULL,
+ GRL_CONTENT_ADDED,
+ FALSE);
+ }
+}
+
+static void
+store_stream (sqlite3 *db, const gchar *podcast_id, Entry *entry)
+{
+ gint r;
+ guint seconds;
+ sqlite3_stmt *sql_stmt = NULL;
+
+ if (!entry->url || entry->url[0] == '\0') {
+ GRL_DEBUG ("Podcast stream has no URL, skipping");
+ return;
+ }
+
+ seconds = duration_to_seconds (entry->duration);
+ GRL_DEBUG ("%s", GRL_SQL_STORE_STREAM);
+ r = sqlite3_prepare_v2 (db,
+ GRL_SQL_STORE_STREAM,
+ strlen (GRL_SQL_STORE_STREAM),
+ &sql_stmt, NULL);
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to store podcast stream '%s': %s",
+ entry->url, sqlite3_errmsg (db));
+ return;
+ }
+
+ sqlite3_bind_text (sql_stmt, 1, podcast_id, -1, SQLITE_STATIC);
+ sqlite3_bind_text (sql_stmt, 2, entry->url, -1, SQLITE_STATIC);
+ sqlite3_bind_text (sql_stmt, 3, entry->title, -1, SQLITE_STATIC);
+ sqlite3_bind_int (sql_stmt, 4, seconds);
+ sqlite3_bind_text (sql_stmt, 5, entry->mime, -1, SQLITE_STATIC);
+ sqlite3_bind_text (sql_stmt, 6, entry->published, -1, SQLITE_STATIC);
+ sqlite3_bind_text (sql_stmt, 7, entry->summary, -1, SQLITE_STATIC);
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ if (r != SQLITE_DONE) {
+ GRL_WARNING ("Failed to store podcast stream '%s': %s",
+ entry->url, sqlite3_errmsg (db));
+ }
+
+ sqlite3_finalize (sql_stmt);
+}
+
+static void
+parse_entry (xmlDocPtr doc, xmlNodePtr entry, Entry *data)
+{
+ xmlNodePtr node;
+ node = entry->xmlChildrenNode;
+ while (node) {
+ if (!xmlStrcmp (node->name, (const xmlChar *) "title")) {
+ data->title =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "enclosure")) {
+ data->id = (gchar *) xmlGetProp (node, (xmlChar *) "url");
+ data->url = g_strdup (data->id);
+ data->mime = (gchar *) xmlGetProp (node, (xmlChar *) "type");
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "summary")) {
+ data->summary =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "pubDate")) {
+ data->published =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ } else if (!xmlStrcmp (node->name, (const xmlChar *) "duration")) {
+ data->duration =
+ (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
+ }
+ node = node->next;
+ }
+}
+
+static void
+touch_podcast (sqlite3 *db, const gchar *podcast_id)
+{
+ gint r;
+ gchar *sql, *sql_error;
+ GTimeVal now;
+ gchar *now_str;
+
+ GRL_DEBUG ("touch_podcast");
+
+ g_get_current_time (&now);
+ now_str = g_time_val_to_iso8601 (&now);
+
+ sql = g_strdup_printf (GRL_SQL_TOUCH_PODCAST, now_str, podcast_id);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_exec (db, sql, NULL, NULL, &sql_error);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to touch podcast, '%s': %s", podcast_id, sql_error);
+ sqlite3_free (sql_error);
+ return;
+ }
+}
+
+static gboolean
+parse_entry_idle (gpointer user_data)
+{
+ OperationSpecParse *osp = (OperationSpecParse *) user_data;
+ xmlNodeSetPtr nodes;
+ guint remaining;
+ GrlMedia *media;
+
+ nodes = osp->xpathObj->nodesetval;
+
+ /* Parse entry */
+ Entry *entry = g_slice_new0 (Entry);
+ if (nodes->nodeTab) {
+ parse_entry (osp->doc, nodes->nodeTab[osp->parse_index], entry);
+ }
+ if (0) print_entry (entry);
+
+ /* Check if entry is valid */
+ if (!entry->url || entry->url[0] == '\0') {
+ GRL_DEBUG ("Podcast stream has no URL, skipping");
+ } else {
+ /* Provide results to user as fast as possible */
+ if (osp->parse_valid_index >= osp->os->skip &&
+ osp->parse_valid_index < osp->os->skip + osp->os->count) {
+ media = build_media_from_entry (entry);
+ remaining = osp->os->skip + osp->os->count - osp->parse_valid_index - 1;
+
+ /* Hack: if we emit the last result now the UI may request more results
+ right away while we are still parsing the XML, so we keep the last
+ result until we are done processing the whole feed, this way when
+ the next query arrives all the stuff is stored in the database
+ and the query can be resolved normally */
+ if (remaining > 0) {
+ osp->os->callback (osp->os->source,
+ osp->os->operation_id,
+ media,
+ remaining,
+ osp->os->user_data,
+ NULL);
+ } else {
+ osp->last_media = media;
+ }
+ }
+
+ osp->parse_valid_index++;
+
+ /* And store stream in database cache */
+ store_stream (GRL_PODCASTS_SOURCE (osp->os->source)->priv->db,
+ osp->os->media_id, entry);
+ }
+
+ osp->parse_index++;
+ free_entry (entry);
+
+ if (osp->parse_index >= osp->parse_count) {
+ /* Send last result */
+ osp->os->callback (osp->os->source,
+ osp->os->operation_id,
+ osp->last_media,
+ 0,
+ osp->os->user_data,
+ NULL);
+ /* Notify about changes */
+ if (GRL_PODCASTS_SOURCE (osp->os->source)->priv->notify_changes) {
+ media = grl_media_box_new ();
+ grl_media_set_id (media, osp->os->media_id);
+ grl_media_source_notify_change (osp->os->source,
+ media,
+ GRL_CONTENT_CHANGED,
+ FALSE);
+ g_object_unref (media);
+ }
+ g_slice_free (OperationSpec, osp->os);
+ xmlXPathFreeObject (osp->xpathObj);
+ xmlXPathFreeContext (osp->xpathCtx);
+ xmlFreeDoc (osp->doc);
+ g_slice_free (OperationSpecParse, osp);
+ }
+
+ return osp->parse_index < osp->parse_count;
+}
+
+static void
+parse_feed (OperationSpec *os, const gchar *str, GError **error)
+{
+ GrlMedia *podcast = NULL;
+ xmlDocPtr doc = NULL;
+ xmlXPathContextPtr xpathCtx = NULL;
+ xmlXPathObjectPtr xpathObj = NULL;
+ guint stream_count;
+
+ GRL_DEBUG ("parse_feed");
+
+ doc = xmlParseDoc ((xmlChar *) str);
+ if (!doc) {
+ *error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to parse podcast contents");
+ goto free_resources;
+ }
+
+ /* Get feed stream list */
+ xpathCtx = xmlXPathNewContext (doc);
+ if (!xpathCtx) {
+ *error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to parse podcast contents");
+ goto free_resources;
+ }
+
+ xpathObj = xmlXPathEvalExpression ((xmlChar *) "/rss/channel/item",
+ xpathCtx);
+ if(xpathObj == NULL) {
+ *error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to parse podcast contents");
+ goto free_resources;
+ }
+
+ /* Feed is ok, let's process it */
+
+ /* First, remove old entries for this podcast */
+ remove_podcast_streams (GRL_PODCASTS_SOURCE (os->source)->priv->db,
+ os->media_id, error);
+ if (*error) {
+ (*error)->code = os->error_code;
+ goto free_resources;
+ }
+
+ /* Then update the last_refreshed date of the podcast */
+ touch_podcast (GRL_PODCASTS_SOURCE (os->source)->priv->db, os->media_id);
+
+ /* If the feed contains no streams, notify and bail out */
+ stream_count = xpathObj->nodesetval ? xpathObj->nodesetval->nodeNr : 0;
+ GRL_DEBUG ("Got %d streams", stream_count);
+
+ if (stream_count <= 0) {
+ if (GRL_PODCASTS_SOURCE (os->source)->priv->notify_changes) {
+ podcast = grl_media_box_new ();
+ grl_media_set_id (podcast, os->media_id);
+ grl_media_source_notify_change (os->source,
+ podcast,
+ GRL_CONTENT_CHANGED,
+ FALSE);
+ g_object_unref (podcast);
+ }
+ os->callback (os->source,
+ os->operation_id,
+ NULL,
+ 0,
+ os->user_data,
+ NULL);
+ goto free_resources;
+ }
+
+ /* Otherwise parse the streams in idle loop to prevent blocking */
+ OperationSpecParse *osp = g_slice_new0 (OperationSpecParse);
+ osp->os = os;
+ osp->doc = doc;
+ osp->xpathCtx = xpathCtx;
+ osp->xpathObj = xpathObj;
+ osp->parse_count = stream_count;
+ g_idle_add (parse_entry_idle, osp);
+ return;
+
+ free_resources:
+ if (xpathObj)
+ xmlXPathFreeObject (xpathObj);
+ if (xpathCtx)
+ xmlXPathFreeContext (xpathCtx);
+ if (doc)
+ xmlFreeDoc (doc);
+}
+
+static void
+read_feed_cb (gchar *xmldata, gpointer user_data)
+{
+ GError *error = NULL;
+ OperationSpec *os = (OperationSpec *) user_data;
+
+ if (!xmldata) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Failed to read data from podcast");
+ } else {
+ parse_feed (os, xmldata, &error);
+ }
+
+ if (error) {
+ os->callback (os->source,
+ os->operation_id,
+ NULL,
+ 0,
+ os->user_data,
+ error);
+ g_error_free (error);
+ g_slice_free (OperationSpec, os);
+ }
+}
+
+static sqlite3_stmt *
+get_podcast_info (sqlite3 *db, const gchar *podcast_id)
+{
+ gint r;
+ sqlite3_stmt *sql_stmt = NULL;
+ gchar *sql;
+
+ GRL_DEBUG ("get_podcast_info");
+
+ sql = g_strdup_printf (GRL_SQL_GET_PODCAST_BY_ID, podcast_id);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to retrieve podcast '%s': %s",
+ podcast_id, sqlite3_errmsg (db));
+ return NULL;
+ }
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ if (r == SQLITE_ROW) {
+ return sql_stmt;
+ } else {
+ GRL_WARNING ("Failed to retrieve podcast information: %s",
+ sqlite3_errmsg (db));
+ sqlite3_finalize (sql_stmt);
+ return NULL;
+ }
+}
+
+static void
+produce_podcast_contents (OperationSpec *os)
+{
+ sqlite3_stmt *sql_stmt = NULL;
+ sqlite3 *db;
+ GError *error;
+ gchar *url;
+
+ GRL_DEBUG ("produce_podcast_contents");
+
+ /* First we get some information about the podcast */
+ db = GRL_PODCASTS_SOURCE (os->source)->priv->db;
+ sql_stmt = get_podcast_info (db, os->media_id);
+ if (sql_stmt) {
+ gchar *lr_str;
+ GTimeVal lr;
+ GTimeVal now;
+
+ /* Check if we have to refresh the podcast */
+ lr_str = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_LAST_REFRESHED);
+ GRL_DEBUG ("Podcast last-refreshed: '%s'", lr_str);
+ g_time_val_from_iso8601 (lr_str ? lr_str : "", &lr);
+ g_get_current_time (&now);
+ now.tv_sec -= CACHE_DURATION;
+ if (now.tv_sec >= lr.tv_sec) {
+ /* We have to read the podcast feed again */
+ GRL_DEBUG ("Refreshing podcast '%s'...", os->media_id);
+ url = g_strdup ((gchar *) sqlite3_column_text (sql_stmt, PODCAST_URL));
+ read_url_async (GRL_PODCASTS_SOURCE (os->source), url, read_feed_cb, os);
+ g_free (url);
+ } else {
+ /* We can read the podcast entries from the database cache */
+ produce_podcast_contents_from_db (os);
+ g_slice_free (OperationSpec, os);
+ }
+ sqlite3_finalize (sql_stmt);
+ } else {
+ error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to retrieve podcast information");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+ g_slice_free (OperationSpec, os);
+ }
+
+}
+
+static void
+produce_podcasts (OperationSpec *os)
+{
+ gint r;
+ sqlite3_stmt *sql_stmt = NULL;
+ sqlite3 *db;
+ GrlMedia *media;
+ GError *error = NULL;
+ GList *medias = NULL;
+ guint count = 0;
+ GList *iter;
+ gchar *sql;
+
+ GRL_DEBUG ("produce_podcasts");
+
+ db = GRL_PODCASTS_SOURCE (os->source)->priv->db;
+
+ if (os->is_query) {
+ /* Query */
+ sql = g_strdup_printf (GRL_SQL_GET_PODCASTS_BY_QUERY,
+ os->text, os->count, os->skip);
+ } else {
+ /* Browse */
+ sql = g_strdup_printf (GRL_SQL_GET_PODCASTS, os->count, os->skip);
+ }
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to retrieve podcasts: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to retrieve podcasts list");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+ goto free_resources;
+ }
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ while (r == SQLITE_ROW) {
+ media = build_media_from_stmt (NULL, sql_stmt, TRUE);
+ medias = g_list_prepend (medias, media);
+ count++;
+ r = sqlite3_step (sql_stmt);
+ }
+
+ if (r != SQLITE_DONE) {
+ GRL_WARNING ("Failed to retrieve podcasts: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to retrieve podcasts list");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+ goto free_resources;
+ }
+
+ if (count > 0) {
+ medias = g_list_reverse (medias);
+ iter = medias;
+ while (iter) {
+ media = GRL_MEDIA (iter->data);
+ os->callback (os->source,
+ os->operation_id,
+ media,
+ --count,
+ os->user_data,
+ NULL);
+ iter = g_list_next (iter);
+ }
+ g_list_free (medias);
+ } else {
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
+ }
+
+ free_resources:
+ if (sql_stmt)
+ sqlite3_finalize (sql_stmt);
+}
+
+static void
+stream_metadata (GrlMediaSourceMetadataSpec *ms)
+{
+ gint r;
+ sqlite3_stmt *sql_stmt = NULL;
+ sqlite3 *db;
+ GError *error = NULL;
+ gchar *sql;
+ const gchar *id;
+
+ GRL_DEBUG ("stream_metadata");
+
+ db = GRL_PODCASTS_SOURCE (ms->source)->priv->db;
+
+ id = grl_media_get_id (ms->media);
+ sql = g_strdup_printf (GRL_SQL_GET_PODCAST_STREAM, id);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to get podcast stream: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Failed to get podcast stream metadata");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
+
+ if (r == SQLITE_ROW) {
+ build_media_from_stmt (ms->media, sql_stmt, FALSE);
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ } else {
+ GRL_WARNING ("Failed to get podcast stream: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Failed to get podcast stream metadata");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ }
+
+ sqlite3_finalize (sql_stmt);
+}
+
+static void
+podcast_metadata (GrlMediaSourceMetadataSpec *ms)
+{
+ sqlite3_stmt *sql_stmt = NULL;
+ sqlite3 *db;
+ GError *error = NULL;
+ const gchar *id;
+
+ GRL_DEBUG ("podcast_metadata");
+
+ db = GRL_PODCASTS_SOURCE (ms->source)->priv->db;
+
+ id = grl_media_get_id (ms->media);
+ if (!id) {
+ /* Root category: special case */
+ grl_media_set_title (ms->media, GRL_ROOT_TITLE);
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ return;
+ }
+
+ sql_stmt = get_podcast_info (db, id);
+
+ if (sql_stmt) {
+ build_media_from_stmt (ms->media, sql_stmt, TRUE);
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ sqlite3_finalize (sql_stmt);
+ } else {
+ GRL_WARNING ("Failed to get podcast: %s", sqlite3_errmsg (db));
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Failed to get podcast metadata");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ }
+}
+
+static gboolean
+media_id_is_podcast (const gchar *id)
+{
+ return g_ascii_strtoll (id, NULL, 10) != 0;
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_podcasts_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_CHILDCOUNT,
+ GRL_METADATA_KEY_SITE,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_podcasts_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ GRL_DEBUG ("grl_podcasts_source_browse");
+
+ OperationSpec *os;
+ GrlPodcastsSource *podcasts_source;
+ GError *error = NULL;
+
+ podcasts_source = GRL_PODCASTS_SOURCE (source);
+ if (!podcasts_source->priv->db) {
+ GRL_WARNING ("Can't execute operation: no database connection.");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "No database connection");
+ bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* Configure browse operation */
+ os = g_slice_new0 (OperationSpec);
+ os->source = bs->source;
+ os->operation_id = bs->browse_id;
+ os->media_id = grl_media_get_id (bs->container);
+ os->count = bs->count;
+ os->skip = bs->skip;
+ os->callback = bs->callback;
+ os->user_data = bs->user_data;
+ os->error_code = GRL_CORE_ERROR_BROWSE_FAILED;
+
+ if (!os->media_id) {
+ /* Browsing podcasts list */
+ produce_podcasts (os);
+ g_slice_free (OperationSpec, os);
+ } else {
+ /* Browsing a particular podcast. We may need to parse
+ the feed (async) and in that case we will need to keep
+ os, so we do not free os here */
+ produce_podcast_contents (os);
+ }
+}
+
+static void
+grl_podcasts_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss)
+{
+ GRL_DEBUG ("grl_podcasts_source_search");
+
+ GrlPodcastsSource *podcasts_source;
+ OperationSpec *os;
+ GError *error = NULL;
+
+ podcasts_source = GRL_PODCASTS_SOURCE (source);
+ if (!podcasts_source->priv->db) {
+ GRL_WARNING ("Can't execute operation: no database connection.");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_QUERY_FAILED,
+ "No database connection");
+ ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ os = g_slice_new0 (OperationSpec);
+ os->source = ss->source;
+ os->operation_id = ss->search_id;
+ os->text = ss->text;
+ os->count = ss->count;
+ os->skip = ss->skip;
+ os->callback = ss->callback;
+ os->user_data = ss->user_data;
+ os->is_query = TRUE;
+ os->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
+ produce_podcast_contents_from_db (os);
+ g_slice_free (OperationSpec, os);
+}
+
+static void
+grl_podcasts_source_query (GrlMediaSource *source, GrlMediaSourceQuerySpec *qs)
+{
+ GRL_DEBUG ("grl_podcasts_source_query");
+
+ GrlPodcastsSource *podcasts_source;
+ OperationSpec *os;
+ GError *error = NULL;
+
+ podcasts_source = GRL_PODCASTS_SOURCE (source);
+ if (!podcasts_source->priv->db) {
+ GRL_WARNING ("Can't execute operation: no database connection.");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_QUERY_FAILED,
+ "No database connection");
+ qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ os = g_slice_new0 (OperationSpec);
+ os->source = qs->source;
+ os->operation_id = qs->query_id;
+ os->text = qs->query;
+ os->count = qs->count;
+ os->skip = qs->skip;
+ os->callback = qs->callback;
+ os->user_data = qs->user_data;
+ os->is_query = TRUE;
+ os->error_code = GRL_CORE_ERROR_QUERY_FAILED;
+ produce_podcasts (os);
+ g_slice_free (OperationSpec, os);
+}
+
+static void
+grl_podcasts_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ GRL_DEBUG ("grl_podcasts_source_metadata");
+
+ GrlPodcastsSource *podcasts_source;
+ GError *error = NULL;
+ const gchar *media_id;
+
+ podcasts_source = GRL_PODCASTS_SOURCE (source);
+ if (!podcasts_source->priv->db) {
+ GRL_WARNING ("Can't execute operation: no database connection.");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "No database connection");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ media_id = grl_media_get_id (ms->media);
+ if (!media_id || media_id_is_podcast (media_id)) {
+ podcast_metadata (ms);
+ } else {
+ stream_metadata (ms);
+ }
+}
+
+static void
+grl_podcasts_source_store (GrlMediaSource *source, GrlMediaSourceStoreSpec *ss)
+{
+ GRL_DEBUG ("grl_podcasts_source_store");
+ GError *error = NULL;
+ if (GRL_IS_MEDIA_BOX (ss->media)) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_STORE_FAILED,
+ "Cannot create containers. Only feeds are accepted.");
+ } else {
+ store_podcast (GRL_PODCASTS_SOURCE (ss->source),
+ ss->media,
+ &error);
+ }
+ ss->callback (ss->source, ss->parent, ss->media, ss->user_data, error);
+ if (error) {
+ g_error_free (error);
+ }
+}
+
+static void
+grl_podcasts_source_remove (GrlMediaSource *source,
+ GrlMediaSourceRemoveSpec *rs)
+{
+ GRL_DEBUG ("grl_podcasts_source_remove");
+ GError *error = NULL;
+ if (media_id_is_podcast (rs->media_id)) {
+ remove_podcast (GRL_PODCASTS_SOURCE (rs->source),
+ rs->media_id, &error);
+ } else {
+ remove_stream (GRL_PODCASTS_SOURCE (rs->source),
+ rs->media_id, &error);
+ }
+ rs->callback (rs->source, rs->media, rs->user_data, error);
+ if (error) {
+ g_error_free (error);
+ }
+}
+
+static gboolean
+grl_podcasts_source_notify_change_start (GrlMediaSource *source,
+ GError **error)
+{
+ GrlPodcastsSource *podcasts_source = GRL_PODCASTS_SOURCE (source);
+
+ podcasts_source->priv->notify_changes = TRUE;
+
+ return TRUE;
+}
+
+static gboolean
+grl_podcasts_source_notify_change_stop (GrlMediaSource *source,
+ GError **error)
+{
+ GrlPodcastsSource *podcasts_source = GRL_PODCASTS_SOURCE (source);
+
+ podcasts_source->priv->notify_changes = FALSE;
+
+ return TRUE;
+}
diff --git a/src/media/podcasts/grl-podcasts.h b/src/media/podcasts/grl-podcasts.h
new file mode 100644
index 0000000..039ba64
--- /dev/null
+++ b/src/media/podcasts/grl-podcasts.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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_PODCASTS_SOURCE_H_
+#define _GRL_PODCASTS_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_PODCASTS_SOURCE_TYPE \
+ (grl_podcasts_source_get_type ())
+
+#define GRL_PODCASTS_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_PODCASTS_SOURCE_TYPE, \
+ GrlPodcastsSource))
+
+#define GRL_IS_PODCASTS_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_PODCASTS_SOURCE_TYPE))
+
+#define GRL_PODCASTS_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_PODCASTS_SOURCE_TYPE, \
+ GrlPodcastsSourceClass))
+
+#define GRL_IS_PODCASTS_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_PODCASTS_SOURCE_TYPE))
+
+#define GRL_PODCASTS_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_PODCASTS_SOURCE_TYPE, \
+ GrlPodcastsSourceClass))
+
+typedef struct _GrlPodcastsPrivate GrlPodcastsPrivate;
+typedef struct _GrlPodcastsSource GrlPodcastsSource;
+
+struct _GrlPodcastsSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlPodcastsPrivate *priv;
+};
+
+typedef struct _GrlPodcastsSourceClass GrlPodcastsSourceClass;
+
+struct _GrlPodcastsSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_podcasts_source_get_type (void);
+
+#endif /* _GRL_PODCASTS_SOURCE_H_ */
diff --git a/src/media/podcasts/grl-podcasts.xml b/src/media/podcasts/grl-podcasts.xml
new file mode 100644
index 0000000..1222f5c
--- /dev/null
+++ b/src/media/podcasts/grl-podcasts.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Podcasts</name>
+ <description>A plugin for browsing podcasts</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/shoutcast/Makefile.am b/src/media/shoutcast/Makefile.am
new file mode 100644
index 0000000..3940692
--- /dev/null
+++ b/src/media/shoutcast/Makefile.am
@@ -0,0 +1,37 @@
+#
+# Makefile.am
+#
+# Author: Juan A. Suarez Romero <jasuarez igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlshoutcast.la
+
+libgrlshoutcast_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GRLNET_CFLAGS) \
+ $(XML_CFLAGS)
+
+libgrlshoutcast_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GRLNET_LIBS) \
+ $(XML_LIBS)
+
+libgrlshoutcast_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlshoutcast_la_SOURCES = grl-shoutcast.c grl-shoutcast.h
+
+libdir=$(GRL_PLUGINS_DIR)
+
+shoutcastxmldir = $(GRL_PLUGINS_CONF_DIR)
+shoutcastxml_DATA = $(SHOUTCAST_PLUGIN_ID).xml
+
+EXTRA_DIST = $(shoutcastxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/shoutcast/grl-shoutcast.c b/src/media/shoutcast/grl-shoutcast.c
new file mode 100644
index 0000000..6b08928
--- /dev/null
+++ b/src/media/shoutcast/grl-shoutcast.c
@@ -0,0 +1,727 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
+#include <net/grl-net.h>
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#include "grl-shoutcast.h"
+
+#define EXPIRE_CACHE_TIMEOUT 300
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT shoutcast_log_domain
+GRL_LOG_DOMAIN_STATIC(shoutcast_log_domain);
+
+/* ------ SHOUTcast API ------ */
+
+#define SHOUTCAST_BASE_ENTRY "http://yp.shoutcast.com"
+
+#define SHOUTCAST_GET_GENRES SHOUTCAST_BASE_ENTRY "/sbin/newxml.phtml"
+#define SHOUTCAST_GET_RADIOS SHOUTCAST_GET_GENRES "?genre=%s&limit=%u"
+#define SHOUTCAST_SEARCH_RADIOS SHOUTCAST_GET_GENRES "?search=%s&limit=%u"
+#define SHOUTCAST_TUNE SHOUTCAST_BASE_ENTRY "/sbin/tunein-station.pls?id=%s"
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID SHOUTCAST_PLUGIN_ID
+
+#define SOURCE_ID "grl-shoutcast"
+#define SOURCE_NAME "SHOUTcast"
+#define SOURCE_DESC "A source for browsing SHOUTcast radios"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+typedef struct {
+ GrlMedia *media;
+ GrlMediaSource *source;
+ GrlMediaSourceMetadataCb metadata_cb;
+ GrlMediaSourceResultCb result_cb;
+ gboolean cancelled;
+ gboolean cache;
+ gchar *filter_entry;
+ gchar *genre;
+ gint error_code;
+ gint operation_id;
+ gint to_send;
+ gpointer user_data;
+ guint count;
+ guint skip;
+ xmlDocPtr xml_doc;
+ xmlNodePtr xml_entries;
+} OperationData;
+
+static GrlNetWc *wc = NULL;
+static GCancellable *cancellable;
+static gchar *cached_page = NULL;
+static gboolean cached_page_expired = TRUE;
+
+static GrlShoutcastSource *grl_shoutcast_source_new (void);
+
+gboolean grl_shoutcast_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+static const GList *grl_shoutcast_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_shoutcast_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms);
+
+static void grl_shoutcast_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+
+static void grl_shoutcast_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+
+static void grl_shoutcast_source_cancel (GrlMediaSource *source,
+ guint operation_id);
+
+static void read_url_async (const gchar *url, OperationData *op_data);
+
+static void grl_shoutcast_source_finalize (GObject *object);
+
+/* =================== SHOUTcast Plugin =============== */
+
+gboolean
+grl_shoutcast_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (shoutcast_log_domain, "shoutcast");
+
+ GRL_DEBUG ("shoutcast_plugin_init");
+
+ GrlShoutcastSource *source = grl_shoutcast_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_shoutcast_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== SHOUTcast GObject ================ */
+
+static GrlShoutcastSource *
+grl_shoutcast_source_new (void)
+{
+ GRL_DEBUG ("grl_shoutcast_source_new");
+ return g_object_new (GRL_SHOUTCAST_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_shoutcast_source_class_init (GrlShoutcastSourceClass * klass)
+{
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ source_class->metadata = grl_shoutcast_source_metadata;
+ source_class->browse = grl_shoutcast_source_browse;
+ source_class->search = grl_shoutcast_source_search;
+ source_class->cancel = grl_shoutcast_source_cancel;
+ metadata_class->supported_keys = grl_shoutcast_source_supported_keys;
+ gobject_class->finalize = grl_shoutcast_source_finalize;
+}
+
+static void
+grl_shoutcast_source_init (GrlShoutcastSource *source)
+{
+}
+
+G_DEFINE_TYPE (GrlShoutcastSource, grl_shoutcast_source, GRL_TYPE_MEDIA_SOURCE);
+
+static void
+grl_shoutcast_source_finalize (GObject *object)
+{
+ if (wc && GRL_IS_NET_WC (wc))
+ g_object_unref (wc);
+
+ if (cancellable && G_IS_CANCELLABLE (cancellable))
+ g_cancellable_cancel (cancellable);
+
+ G_OBJECT_CLASS (grl_shoutcast_source_parent_class)->finalize (object);
+}
+
+/* ======================= Private ==================== */
+
+static gint
+xml_count_nodes (xmlNodePtr node)
+{
+ gint count = 0;
+
+ while (node) {
+ count++;
+ node = node->next;
+ }
+
+ return count;
+}
+
+static GrlMedia *
+build_media_from_genre (OperationData *op_data)
+{
+ GrlMedia *media;
+ gchar *genre_name;
+
+ if (op_data->media) {
+ media = op_data->media;
+ } else {
+ media = grl_media_box_new ();
+ }
+
+ genre_name = (gchar *) xmlGetProp (op_data->xml_entries,
+ (const xmlChar *) "name");
+
+ grl_media_set_id (media, genre_name);
+ grl_media_set_title (media, genre_name);
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_GENRE,
+ genre_name);
+ g_free (genre_name);
+
+ return media;
+}
+
+static GrlMedia *
+build_media_from_station (OperationData *op_data)
+{
+ GrlMedia *media;
+ gchar **station_genres = NULL;
+ gchar *media_id;
+ gchar *media_url;
+ gchar *station_bitrate;
+ gchar *station_genre;
+ gchar *station_genre_field;
+ gchar *station_id;
+ gchar *station_mime;
+ gchar *station_name;
+
+ station_name = (gchar *) xmlGetProp (op_data->xml_entries,
+ (const xmlChar *) "name");
+ station_mime = (gchar *) xmlGetProp (op_data->xml_entries,
+ (const xmlChar *) "mt");
+ station_id = (gchar *) xmlGetProp (op_data->xml_entries,
+ (const xmlChar *) "id");
+ station_bitrate = (gchar *) xmlGetProp (op_data->xml_entries,
+ (const xmlChar *) "br");
+ if (op_data->media) {
+ media = op_data->media;
+ } else {
+ media = grl_media_audio_new ();
+ }
+
+ if (op_data->genre) {
+ station_genre = op_data->genre;
+ } else {
+ station_genre_field = (gchar *) xmlGetProp (op_data->xml_entries,
+ (const xmlChar *) "genre");
+ station_genres = g_strsplit (station_genre_field, " ", -1);
+ g_free (station_genre_field);
+ station_genre = station_genres[0];
+ }
+
+ media_id = g_strconcat (station_genre, "/", station_id, NULL);
+ media_url = g_strdup_printf (SHOUTCAST_TUNE, station_id);
+
+ grl_media_set_id (media, media_id);
+ grl_media_set_title (media, station_name);
+ grl_media_set_mime (media, station_mime);
+ grl_media_audio_set_genre (GRL_MEDIA_AUDIO (media), station_genre);
+ grl_media_set_url (media, media_url);
+ grl_media_audio_set_bitrate (GRL_MEDIA_AUDIO (media),
+ atoi (station_bitrate));
+
+ g_free (station_name);
+ g_free (station_mime);
+ g_free (station_id);
+ g_free (station_bitrate);
+ g_free (media_id);
+ g_free (media_url);
+ if (station_genres) {
+ g_strfreev (station_genres);
+ }
+
+ return media;
+}
+
+static gboolean
+send_media (OperationData *op_data, GrlMedia *media)
+{
+ if (!op_data->cancelled) {
+ op_data->result_cb (op_data->source,
+ op_data->operation_id,
+ media,
+ --op_data->to_send,
+ op_data->user_data,
+ NULL);
+
+ op_data->xml_entries = op_data->xml_entries->next;
+ } else {
+ op_data->result_cb (op_data->source,
+ op_data->operation_id,
+ NULL,
+ 0,
+ op_data->user_data,
+ NULL);
+ }
+
+ if (op_data->to_send == 0 || op_data->cancelled) {
+ xmlFreeDoc (op_data->xml_doc);
+ g_slice_free (OperationData, op_data);
+ return FALSE;
+ } else {
+ return TRUE;
+ }
+}
+
+static gboolean
+send_genrelist_entries (OperationData *op_data)
+{
+ return send_media (op_data,
+ build_media_from_genre (op_data));
+}
+
+static gboolean
+send_stationlist_entries (OperationData *op_data)
+{
+ return send_media (op_data,
+ build_media_from_station (op_data));
+}
+
+static void
+xml_parse_result (const gchar *str, OperationData *op_data)
+{
+ GError *error = NULL;
+ gboolean stationlist_result;
+ gchar *xpath_expression;
+ xmlNodePtr node;
+ xmlXPathContextPtr xpath_ctx;
+ xmlXPathObjectPtr xpath_res;
+
+ if (op_data->cancelled) {
+ op_data->result_cb (op_data->source,
+ op_data->operation_id,
+ NULL,
+ 0,
+ op_data->user_data,
+ NULL);
+ g_slice_free (OperationData, op_data);
+ return;
+ }
+
+ op_data->xml_doc = xmlReadMemory (str, xmlStrlen ((xmlChar*) str), NULL, NULL,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ if (!op_data->xml_doc) {
+ error = g_error_new (GRL_CORE_ERROR,
+ op_data->error_code,
+ "Failed to parse SHOUTcast's response");
+ goto finalize;
+ }
+
+ node = xmlDocGetRootElement (op_data->xml_doc);
+ if (!node) {
+ error = g_error_new (GRL_CORE_ERROR,
+ op_data->error_code,
+ "Empty response from SHOUTcast");
+ goto finalize;
+ }
+
+ stationlist_result = (xmlStrcmp (node->name,
+ (const xmlChar *) "stationlist") == 0);
+
+ op_data->xml_entries = node->xmlChildrenNode;
+
+ /* Check if we are interesting only in updating a media (that is, a metadata()
+ operation) or just browsing/searching */
+ if (op_data->media) {
+
+ /* Search for node */
+ xpath_ctx = xmlXPathNewContext (op_data->xml_doc);
+ if (xpath_ctx) {
+ if (stationlist_result) {
+ xpath_expression = g_strdup_printf ("//station[@id = \"%s\"]",
+ op_data->filter_entry);
+ } else {
+ xpath_expression = g_strdup_printf ("//genre[@name = \"%s\"]",
+ op_data->filter_entry);
+ }
+ xpath_res = xmlXPathEvalExpression ((xmlChar *) xpath_expression,
+ xpath_ctx);
+ g_free (xpath_expression);
+
+ if (xpath_res && xpath_res->nodesetval->nodeTab &&
+ xpath_res->nodesetval->nodeTab[0]) {
+ op_data->xml_entries = xpath_res->nodesetval->nodeTab[0];
+ if (stationlist_result) {
+ build_media_from_station (op_data);
+ } else {
+ build_media_from_genre (op_data);
+ }
+ } else {
+ error = g_error_new (GRL_CORE_ERROR,
+ op_data->error_code,
+ "Can not find media '%s'",
+ grl_media_get_id (op_data->media));
+ }
+ if (xpath_res) {
+ xmlXPathFreeObject (xpath_res);
+ }
+ xmlXPathFreeContext (xpath_ctx);
+ } else {
+ error = g_error_new (GRL_CORE_ERROR,
+ op_data->error_code,
+ "Can not build xpath context");
+ }
+
+ op_data->metadata_cb (
+ op_data->source,
+ op_data->media,
+ op_data->user_data,
+ error);
+ goto free_resources;
+ }
+
+ if (stationlist_result) {
+ /* First node is "tunein"; skip it */
+ op_data->xml_entries = op_data->xml_entries->next;
+ }
+
+ /* Skip elements */
+ while (op_data->xml_entries && op_data->skip > 0) {
+ op_data->xml_entries = op_data->xml_entries->next;
+ op_data->skip--;
+ }
+
+ /* Check if there are elements to send*/
+ if (!op_data->xml_entries || op_data->count == 0) {
+ goto finalize;
+ }
+
+ /* Compute how many items are to be sent */
+ op_data->to_send = xml_count_nodes (op_data->xml_entries);
+ if (op_data->to_send > op_data->count) {
+ op_data->to_send = op_data->count;
+ }
+
+ if (stationlist_result) {
+ g_idle_add ((GSourceFunc) send_stationlist_entries, op_data);
+ } else {
+ g_idle_add ((GSourceFunc) send_genrelist_entries, op_data);
+ }
+
+ return;
+
+ finalize:
+ op_data->result_cb (op_data->source,
+ op_data->operation_id,
+ NULL,
+ 0,
+ op_data->user_data,
+ error);
+
+ free_resources:
+ if (op_data->xml_doc) {
+ xmlFreeDoc (op_data->xml_doc);
+ }
+
+ if (op_data->filter_entry) {
+ g_free (op_data->filter_entry);
+ }
+
+ if (error) {
+ g_error_free (error);
+ }
+ g_slice_free (OperationData, op_data);
+}
+
+static gboolean
+expire_cache (gpointer user_data)
+{
+ GRL_DEBUG ("Cached page expired");
+ cached_page_expired = TRUE;
+ return FALSE;
+}
+
+static void
+read_done_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ GError *wc_error = NULL;
+ OperationData *op_data = (OperationData *) user_data;
+ gboolean cache;
+ gchar *content = NULL;
+
+ if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res,
+ &content,
+ NULL,
+ &wc_error)) {
+ error = g_error_new (GRL_CORE_ERROR,
+ op_data->error_code,
+ "Failed to connect SHOUTcast: '%s'",
+ wc_error->message);
+ op_data->result_cb (op_data->source,
+ op_data->operation_id,
+ NULL,
+ 0,
+ op_data->user_data,
+ error);
+ g_error_free (wc_error);
+ g_error_free (error);
+ g_slice_free (OperationData, op_data);
+
+ return;
+ }
+
+ cache = op_data->cache;
+ xml_parse_result (content, op_data);
+ if (cache && cached_page_expired) {
+ GRL_DEBUG ("Caching page");
+ g_free (cached_page);
+ cached_page = g_strdup (content);
+ cached_page_expired = FALSE;
+ g_timeout_add_seconds (EXPIRE_CACHE_TIMEOUT, expire_cache, NULL);
+ }
+}
+
+static gboolean
+read_cached_page (OperationData *op_data)
+{
+ xml_parse_result (cached_page, op_data);
+ return FALSE;
+}
+
+static void
+read_url_async (const gchar *url, OperationData *op_data)
+{
+ if (op_data->cache && !cached_page_expired) {
+ GRL_DEBUG ("Using cached page");
+ g_idle_add ((GSourceFunc) read_cached_page, op_data);
+ } else {
+ if (!wc)
+ wc = grl_net_wc_new ();
+
+ cancellable = g_cancellable_new ();
+ grl_net_wc_request_async (wc, url, cancellable, read_done_cb, op_data);
+ }
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_shoutcast_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_BITRATE,
+ GRL_METADATA_KEY_GENRE,
+ GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_MIME,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_shoutcast_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ const gchar *media_id;
+ gchar **id_tokens;
+ gchar *url = NULL;
+ OperationData *data = NULL;
+
+ /* Unfortunately, shoutcast does not have an API to get information about a
+ station. Thus, steps done to obtain the Content must be repeated. For
+ instance, if we have a Media with id "Pop/1321", it means that it is
+ station #1321 that was obtained after browsing "Pop" category. Thus we have
+ repeat the Pop browsing and get the result with station id 1321. If it
+ doesn't exist (think in results obtained from a search), we do nothing */
+
+ media_id = grl_media_get_id (ms->media);
+
+ /* Check if we need to report about root category */
+ if (!media_id) {
+ grl_media_set_title (ms->media, "SHOUTcast");
+ } else {
+ data = g_slice_new0 (OperationData);
+ data->source = source;
+ data->count = 1;
+ data->metadata_cb = ms->callback;
+ data->user_data = ms->user_data;
+ data->error_code = GRL_CORE_ERROR_METADATA_FAILED;
+ data->media = ms->media;
+
+ id_tokens = g_strsplit (media_id, "/", -1);
+
+ /* Check if Content is a media */
+ if (id_tokens[1]) {
+ data->filter_entry = g_strdup (id_tokens[1]);
+
+ /* Check if result is from a previous search */
+ if (id_tokens[0][0] == '?') {
+ url = g_strdup_printf (SHOUTCAST_SEARCH_RADIOS,
+ id_tokens[0]+1,
+ G_MAXINT);
+ } else {
+ url = g_strdup_printf (SHOUTCAST_GET_RADIOS,
+ id_tokens[0],
+ G_MAXINT);
+ }
+ } else {
+ data->filter_entry = g_strdup (id_tokens[0]);
+ data->cache = TRUE;
+ url = g_strdup (SHOUTCAST_GET_GENRES);
+ }
+
+ g_strfreev (id_tokens);
+ }
+
+ if (url) {
+ read_url_async (url, data);
+ g_free (url);
+ } else {
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ }
+}
+
+static void
+grl_shoutcast_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ OperationData *data;
+ const gchar *container_id;
+ gchar *url;
+
+ GRL_DEBUG ("grl_shoutcast_source_browse");
+
+ data = g_slice_new0 (OperationData);
+ data->source = source;
+ data->operation_id = bs->browse_id;
+ data->result_cb = bs->callback;
+ data->skip = bs->skip;
+ data->count = bs->count;
+ data->user_data = bs->user_data;
+ data->error_code = GRL_CORE_ERROR_BROWSE_FAILED;
+
+ container_id = grl_media_get_id (bs->container);
+
+ /* If it's root category send list of genres; else send list of radios */
+ if (!container_id) {
+ data->cache = TRUE;
+ url = g_strdup (SHOUTCAST_GET_GENRES);
+ } else {
+ url = g_strdup_printf (SHOUTCAST_GET_RADIOS,
+ container_id,
+ bs->skip + bs->count);
+ data->genre = g_strdup (container_id);
+ }
+
+ grl_media_source_set_operation_data (source, bs->browse_id, data);
+
+ read_url_async (url, data);
+
+ g_free (url);
+}
+
+static void
+grl_shoutcast_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss)
+{
+ GError *error;
+ OperationData *data;
+ gchar *url;
+
+ /* Check if there is text to search */
+ if (!ss->text || ss->text[0] == '\0') {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_SEARCH_FAILED,
+ "Search text not specified");
+ ss->callback (ss->source,
+ ss->search_id,
+ NULL,
+ 0,
+ ss->user_data,
+ error);
+
+ g_error_free (error);
+ return;
+ }
+
+ data = g_slice_new0 (OperationData);
+ data->source = source;
+ data->operation_id = ss->search_id;
+ data->result_cb = ss->callback;
+ data->skip = ss->skip;
+ data->count = ss->count;
+ data->user_data = ss->user_data;
+ data->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
+
+ grl_media_source_set_operation_data (source, ss->search_id, data);
+
+ url = g_strdup_printf (SHOUTCAST_SEARCH_RADIOS,
+ ss->text,
+ ss->skip + ss->count);
+
+ read_url_async (url, data);
+
+ g_free (url);
+}
+
+static void
+grl_shoutcast_source_cancel (GrlMediaSource *source, guint operation_id)
+{
+ OperationData *op_data;
+
+ GRL_DEBUG ("grl_shoutcast_source_cancel");
+
+ if (cancellable && G_IS_CANCELLABLE (cancellable))
+ g_cancellable_cancel (cancellable);
+ cancellable = NULL;
+
+ op_data = (OperationData *) grl_media_source_get_operation_data (source, operation_id);
+
+ if (op_data) {
+ op_data->cancelled = TRUE;
+ }
+}
diff --git a/src/media/shoutcast/grl-shoutcast.h b/src/media/shoutcast/grl-shoutcast.h
new file mode 100644
index 0000000..741fac0
--- /dev/null
+++ b/src/media/shoutcast/grl-shoutcast.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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_SHOUTCAST_SOURCE_H_
+#define _GRL_SHOUTCAST_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_SHOUTCAST_SOURCE_TYPE \
+ (grl_shoutcast_source_get_type ())
+
+#define GRL_SHOUTCAST_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_SHOUTCAST_SOURCE_TYPE, \
+ GrlShoutcastSource))
+
+#define GRL_IS_SHOUTCAST_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_SHOUTCAST_SOURCE_TYPE))
+
+#define GRL_SHOUTCAST_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_SHOUTCAST_SOURCE_TYPE, \
+ GrlShoutcastSourceClass))
+
+#define GRL_IS_SHOUTCAST_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_SHOUTCAST_SOURCE_TYPE))
+
+#define GRL_SHOUTCAST_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_SHOUTCAST_SOURCE_TYPE, \
+ GrlShoutcastSourceClass))
+
+typedef struct _GrlShoutcastSource GrlShoutcastSource;
+
+struct _GrlShoutcastSource {
+
+ GrlMediaSource parent;
+
+};
+
+typedef struct _GrlShoutcastSourceClass GrlShoutcastSourceClass;
+
+struct _GrlShoutcastSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_shoutcast_source_get_type (void);
+
+#endif /* _GRL_SHOUTCAST_SOURCE_H_ */
diff --git a/src/media/shoutcast/grl-shoutcast.xml b/src/media/shoutcast/grl-shoutcast.xml
new file mode 100644
index 0000000..2f9e916
--- /dev/null
+++ b/src/media/shoutcast/grl-shoutcast.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>SHOUTcast</name>
+ <description>A plugin for browsing SHOUTcast radios</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/tracker/Makefile.am b/src/media/tracker/Makefile.am
new file mode 100644
index 0000000..5510f5a
--- /dev/null
+++ b/src/media/tracker/Makefile.am
@@ -0,0 +1,36 @@
+#
+# Makefile.am
+#
+# Author: Juan A. Suarez Romero <jasuarez igalia com>
+#
+# Copyright (C) 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrltracker.la
+
+libgrltracker_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(TRACKER_SPARQL_CFLAGS)
+
+libgrltracker_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(TRACKER_SPARQL_LIBS)
+
+libgrltracker_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrltracker_la_SOURCES = \
+ grl-tracker.c \
+ grl-tracker.h
+
+libdir = $(GRL_PLUGINS_DIR)
+trackerxmldir = $(GRL_PLUGINS_CONF_DIR)
+trackerxml_DATA = $(TRACKER_PLUGIN_ID).xml
+
+EXTRA_DIST = $(trackerxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/tracker/grl-tracker.c b/src/media/tracker/grl-tracker.c
new file mode 100644
index 0000000..b07627f
--- /dev/null
+++ b/src/media/tracker/grl-tracker.c
@@ -0,0 +1,1511 @@
+/*
+ * Copyright (C) 2011 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
+#include <string.h>
+#include <gio/gio.h>
+#include <tracker-sparql.h>
+
+#include "grl-tracker.h"
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT tracker_log_domain
+GRL_LOG_DOMAIN_STATIC(tracker_log_domain);
+
+/* ------- Definitions ------- */
+
+#define MEDIA_TYPE "grilo-media-type"
+
+#define RDF_TYPE_ALBUM "nmm#MusicAlbum"
+#define RDF_TYPE_ARTIST "nmm#Artist"
+#define RDF_TYPE_AUDIO "nfo#Audio"
+#define RDF_TYPE_MUSIC "nmm#MusicPiece"
+#define RDF_TYPE_IMAGE "nmm#Photo"
+#define RDF_TYPE_VIDEO "nmm#Video"
+#define RDF_TYPE_BOX "grilo#Box"
+
+/* ---- Plugin information --- */
+
+#define PLUGIN_ID TRACKER_PLUGIN_ID
+
+#define SOURCE_ID "grl-tracker"
+#define SOURCE_NAME "Tracker"
+#define SOURCE_DESC "A plugin for searching multimedia content using Tracker"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+enum {
+ METADATA,
+ BROWSE,
+ QUERY,
+ SEARCH
+};
+
+/* --- Other --- */
+
+#define TRACKER_MOUNTED_DATASOURCES_START \
+ "SELECT nie:dataSource(?urn) AS ?datasource " \
+ "(SELECT nie:url(tracker:mountPoint(?ds)) " \
+ "WHERE { ?urn nie:dataSource ?ds }) " \
+ "(SELECT GROUP_CONCAT(tracker:isMounted(?ds), \",\") " \
+ "WHERE { ?urn nie:dataSource ?ds }) " \
+ "WHERE { ?urn a nfo:FileDataObject . FILTER (?urn IN ("
+
+#define TRACKER_MOUNTED_DATASOURCES_END " ))} GROUP BY (?datasource)"
+
+#define TRACKER_DATASOURCES_REQUEST \
+ "SELECT ?urn nie:dataSource(?urn) AS ?source " \
+ "(SELECT GROUP_CONCAT(nie:url(tracker:mountPoint(?ds)), \",\") " \
+ "WHERE { ?urn nie:dataSource ?ds }) " \
+ "WHERE { " \
+ "?urn tracker:available ?tr . " \
+ "?source a tracker:Volume . " \
+ "FILTER (bound(nie:dataSource(?urn))) " \
+ "} " \
+ "GROUP BY (?source)"
+
+#define TRACKER_QUERY_REQUEST \
+ "SELECT rdf:type(?urn) %s " \
+ "WHERE { %s . %s } " \
+ "ORDER BY DESC(nfo:fileLastModified(?urn)) " \
+ "OFFSET %i " \
+ "LIMIT %i"
+
+#define TRACKER_SEARCH_REQUEST \
+ "SELECT rdf:type(?urn) %s " \
+ "WHERE " \
+ "{ " \
+ "?urn a nfo:Media . " \
+ "?urn tracker:available ?tr . " \
+ "?urn fts:match '%s' . " \
+ "%s " \
+ "} " \
+ "ORDER BY DESC(nfo:fileLastModified(?urn)) " \
+ "OFFSET %i " \
+ "LIMIT %i"
+
+#define TRACKER_SEARCH_ALL_REQUEST \
+ "SELECT rdf:type(?urn) %s " \
+ "WHERE " \
+ "{ " \
+ "?urn a nfo:Media . " \
+ "?urn tracker:available ?tr . " \
+ "%s " \
+ "} " \
+ "ORDER BY DESC(nfo:fileLastModified(?urn)) " \
+ "OFFSET %i " \
+ "LIMIT %i"
+
+#define TRACKER_BROWSE_CATEGORY_REQUEST \
+ "SELECT rdf:type(?urn) %s " \
+ "WHERE " \
+ "{ " \
+ "?urn a %s . " \
+ "?urn tracker:available ?tr . " \
+ "%s " \
+ "} " \
+ "ORDER BY DESC(nfo:fileLastModified(?urn)) " \
+ "OFFSET %i " \
+ "LIMIT %i"
+
+#define TRACKER_METADATA_REQUEST \
+ "SELECT %s " \
+ "WHERE { ?urn a nie:DataObject . FILTER (?urn = <%s>) }"
+
+typedef struct {
+ GrlKeyID grl_key;
+ const gchar *sparql_key_name;
+ const gchar *sparql_key_attr;
+ const gchar *sparql_key_flavor;
+} tracker_grl_sparql_t;
+
+typedef struct {
+ gboolean in_use;
+
+ GHashTable *updated_items;
+ GList *updated_items_list;
+ GList *updated_items_iter;
+
+ /* GList *updated_sources; */
+
+ TrackerSparqlCursor *cursor;
+} tracker_evt_update_t;
+
+struct OperationSpec {
+ GrlMediaSource *source;
+ GrlTrackerSourcePriv *priv;
+ guint operation_id;
+ GCancellable *cancel_op;
+ const GList *keys;
+ guint skip;
+ guint count;
+ guint current;
+ GrlMediaSourceResultCb callback;
+ gpointer user_data;
+ TrackerSparqlCursor *cursor;
+};
+
+enum {
+ PROP_0,
+ PROP_TRACKER_CONNECTION,
+};
+
+struct _GrlTrackerSourcePriv {
+ TrackerSparqlConnection *tracker_connection;
+
+ GHashTable *operations;
+
+ gchar *tracker_datasource;
+};
+
+#define GRL_TRACKER_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_TRACKER_SOURCE_TYPE, \
+ GrlTrackerSourcePriv))
+
+static GrlTrackerSource *grl_tracker_source_new (TrackerSparqlConnection *connection);
+
+static void grl_tracker_source_set_property (GObject *object,
+ guint propid,
+ const GValue *value,
+ GParamSpec *pspec);
+
+static void grl_tracker_source_constructed (GObject *object);
+
+static void grl_tracker_source_finalize (GObject *object);
+
+gboolean grl_tracker_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+static const GList *grl_tracker_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_tracker_source_query (GrlMediaSource *source,
+ GrlMediaSourceQuerySpec *qs);
+
+static void grl_tracker_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms);
+
+static void grl_tracker_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+
+static void grl_tracker_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+
+static void grl_tracker_source_cancel (GrlMediaSource *source,
+ guint operation_id);
+
+static gchar *get_tracker_source_name (const gchar *uri,
+ const gchar *datasource);
+
+static void setup_key_mappings (void);
+
+/* ===================== Globals ================= */
+
+static GHashTable *grl_to_sparql_mapping = NULL;
+static GHashTable *sparql_to_grl_mapping = NULL;
+
+static GVolumeMonitor *volume_monitor = NULL;
+static TrackerSparqlConnection *tracker_connection = NULL;
+static gboolean tracker_per_device_source = FALSE;
+static const GrlPluginInfo *tracker_grl_plugin;
+static guint tracker_dbus_signal_id = 0;
+
+/* =================== Tracker Plugin =============== */
+
+static tracker_evt_update_t *
+tracker_evt_update_new (void)
+{
+ tracker_evt_update_t *evt = g_slice_new0 (tracker_evt_update_t);
+
+ evt->updated_items = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ return evt;
+}
+
+static void
+tracker_evt_update_free (tracker_evt_update_t *evt)
+{
+ if (!evt)
+ return;
+
+ g_hash_table_destroy (evt->updated_items);
+ g_list_free (evt->updated_items_list);
+
+ if (evt->cursor != NULL)
+ g_object_unref (evt->cursor);
+
+ g_slice_free (tracker_evt_update_t, evt);
+}
+
+static void
+grl_tracker_add_source (GrlTrackerSource *source)
+{
+ grl_plugin_registry_register_source (grl_plugin_registry_get_default (),
+ tracker_grl_plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+}
+
+static void
+grl_tracker_del_source (GrlTrackerSource *source)
+{
+ grl_plugin_registry_unregister_source (grl_plugin_registry_get_default (),
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+}
+
+static void
+tracker_evt_update_process_item_cb (GObject *object,
+ GAsyncResult *result,
+ tracker_evt_update_t *evt)
+{
+ const gchar *datasource, *uri;
+ gboolean source_mounted;
+ gchar *source_name = NULL;
+ GrlMediaPlugin *plugin;
+ GError *tracker_error = NULL;
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ if (!tracker_sparql_cursor_next_finish (evt->cursor,
+ result,
+ &tracker_error)) {
+ if (tracker_error != NULL) {
+ GRL_DEBUG ("\terror in parsing : %s", tracker_error->message);
+ g_error_free (tracker_error);
+ } else {
+ GRL_DEBUG ("\tend of parsing :)");
+ }
+
+ tracker_evt_update_free (evt);
+ return;
+ }
+
+ datasource = tracker_sparql_cursor_get_string (evt->cursor, 0, NULL);
+ uri = tracker_sparql_cursor_get_string (evt->cursor, 1, NULL);
+ source_mounted = tracker_sparql_cursor_get_boolean (evt->cursor, 2);
+
+ plugin = grl_plugin_registry_lookup_source (grl_plugin_registry_get_default (),
+ datasource);
+
+ GRL_DEBUG ("\tdatasource=%s uri=%s mounted=%i plugin=%p",
+ datasource, uri, source_mounted, plugin);
+
+ if (source_mounted) {
+ if (plugin == NULL) {
+ source_name = get_tracker_source_name (uri, datasource);
+
+ plugin = g_object_new (GRL_TRACKER_SOURCE_TYPE,
+ "source-id", datasource,
+ "source-name", source_name,
+ "source-desc", SOURCE_DESC,
+ "tracker-connection", tracker_connection,
+ NULL);
+ grl_tracker_add_source (GRL_TRACKER_SOURCE (plugin));
+ g_free (source_name);
+ } else {
+ GRL_DEBUG ("\tChanges on source %p / %s", plugin, datasource);
+ }
+ } else if (!source_mounted && plugin != NULL) {
+ grl_tracker_del_source (GRL_TRACKER_SOURCE (plugin));
+ }
+
+ tracker_sparql_cursor_next_async (evt->cursor, NULL,
+ (GAsyncReadyCallback) tracker_evt_update_process_item_cb,
+ (gpointer) evt);
+}
+
+static void
+tracker_evt_update_process_cb (GObject *object,
+ GAsyncResult *result,
+ tracker_evt_update_t *evt)
+{
+ GError *tracker_error = NULL;
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ evt->cursor = tracker_sparql_connection_query_finish (tracker_connection,
+ result, NULL);
+
+ if (tracker_error != NULL) {
+ GRL_WARNING ("Could not execute sparql query: %s", tracker_error->message);
+
+ g_error_free (tracker_error);
+ tracker_evt_update_free (evt);
+ return;
+ }
+
+ tracker_sparql_cursor_next_async (evt->cursor, NULL,
+ (GAsyncReadyCallback) tracker_evt_update_process_item_cb,
+ (gpointer) evt);
+}
+
+static void
+tracker_evt_update_process (tracker_evt_update_t *evt)
+{
+ GString *request_str = g_string_new (TRACKER_MOUNTED_DATASOURCES_START);
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ evt->updated_items_iter = evt->updated_items_list;
+ g_string_append_printf (request_str, "%i",
+ GPOINTER_TO_INT (evt->updated_items_iter->data));
+ evt->updated_items_iter = evt->updated_items_iter->next;
+
+ while (evt->updated_items_iter != NULL) {
+ g_string_append_printf (request_str, ", %i",
+ GPOINTER_TO_INT (evt->updated_items_iter->data));
+ evt->updated_items_iter = evt->updated_items_iter->next;
+ }
+
+ g_string_append (request_str, TRACKER_MOUNTED_DATASOURCES_END);
+
+ GRL_DEBUG ("\trequest : %s", request_str->str);
+
+ tracker_sparql_connection_query_async (tracker_connection,
+ request_str->str,
+ NULL,
+ (GAsyncReadyCallback) tracker_evt_update_process_cb,
+ evt);
+ g_string_free (request_str, TRUE);
+}
+
+static void
+tracker_dbus_signal_cb (GDBusConnection *connection,
+ const gchar *sender_name,
+ const gchar *object_path,
+ const gchar *interface_name,
+ const gchar *signal_name,
+ GVariant *parameters,
+ gpointer user_data)
+
+{
+ gchar *class_name;
+ gint graph = 0, subject = 0, predicate = 0, object = 0, subject_state;
+ GVariantIter *iter1, *iter2;
+ tracker_evt_update_t *evt = tracker_evt_update_new ();
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ g_variant_get (parameters, "(&sa(iiii)a(iiii))", &class_name, &iter1, &iter2);
+
+ GRL_DEBUG ("\tTracker update event for class=%s ins=%lu del=%lu",
+ class_name,
+ (unsigned long) g_variant_iter_n_children (iter1),
+ (unsigned long) g_variant_iter_n_children (iter2));
+
+ while (g_variant_iter_loop (iter1, "(iiii)", &graph,
+ &subject, &predicate, &object)) {
+ subject_state = GPOINTER_TO_INT (g_hash_table_lookup (evt->updated_items,
+ GSIZE_TO_POINTER (subject)));
+
+ if (subject_state == 0) {
+ g_hash_table_insert (evt->updated_items,
+ GSIZE_TO_POINTER (subject),
+ GSIZE_TO_POINTER (1));
+ evt->updated_items_list = g_list_append (evt->updated_items_list,
+ GSIZE_TO_POINTER (subject));
+ } else if (subject_state == 2)
+ evt->updated_items_list = g_list_append (evt->updated_items_list,
+ GSIZE_TO_POINTER (subject));
+ }
+ g_variant_iter_free (iter1);
+
+
+ while (g_variant_iter_loop (iter2, "(iiii)", &graph,
+ &subject, &predicate, &object)) {
+ subject_state = GPOINTER_TO_INT (g_hash_table_lookup (evt->updated_items,
+ GSIZE_TO_POINTER (subject)));
+
+ if (subject_state == 0) {
+ g_hash_table_insert (evt->updated_items,
+ GSIZE_TO_POINTER (subject),
+ GSIZE_TO_POINTER (1));
+ evt->updated_items_list = g_list_append (evt->updated_items_list,
+ GSIZE_TO_POINTER (subject));
+ } else if (subject_state == 2)
+ evt->updated_items_list = g_list_append (evt->updated_items_list,
+ GSIZE_TO_POINTER (subject));
+ }
+ g_variant_iter_free (iter2);
+
+ evt->updated_items_iter = evt->updated_items_list;
+
+ GRL_DEBUG ("\t%u elements updated", g_hash_table_size (evt->updated_items));
+ GRL_DEBUG ("\t%u elements updated (list)",
+ g_list_length (evt->updated_items_list));
+
+ tracker_evt_update_process (evt);
+}
+
+static void
+tracker_dbus_start_watch (void)
+{
+ GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+
+ tracker_dbus_signal_id = g_dbus_connection_signal_subscribe (connection,
+ TRACKER_DBUS_SERVICE,
+ TRACKER_DBUS_INTERFACE_RESOURCES,
+ "GraphUpdated",
+ TRACKER_DBUS_OBJECT_RESOURCES,
+ NULL,
+ G_DBUS_SIGNAL_FLAGS_NONE,
+ tracker_dbus_signal_cb,
+ NULL,
+ NULL);
+}
+
+static void
+tracker_get_datasource_cb (GObject *object,
+ GAsyncResult *result,
+ TrackerSparqlCursor *cursor)
+{
+ const gchar *datasource, *uri;
+ gchar *source_name;
+ GError *tracker_error = NULL;
+ GrlTrackerSource *source;
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ if (!tracker_sparql_cursor_next_finish (cursor, result, &tracker_error)) {
+ if (tracker_error == NULL) {
+ GRL_DEBUG ("\tEnd of parsing of devices");
+ } else {
+ GRL_DEBUG ("\tError while parsing devices: %s", tracker_error->message);
+ g_error_free (tracker_error);
+ }
+ g_object_unref (G_OBJECT (cursor));
+ return;
+ }
+
+ datasource = tracker_sparql_cursor_get_string (cursor, 1, NULL);
+ uri = tracker_sparql_cursor_get_string (cursor, 2, NULL);
+ source = GRL_TRACKER_SOURCE (grl_plugin_registry_lookup_source (grl_plugin_registry_get_default (),
+ datasource));
+
+ if (source == NULL) {
+ source_name = get_tracker_source_name (uri, datasource); /* TODO: get a better name */
+
+ GRL_DEBUG ("\tnew datasource: urn=%s uri=%s\n", datasource, uri);
+
+ source = g_object_new (GRL_TRACKER_SOURCE_TYPE,
+ "source-id", datasource,
+ "source-name", source_name,
+ "source-desc", SOURCE_DESC,
+ "tracker-connection", tracker_connection,
+ NULL);
+ grl_plugin_registry_register_source (grl_plugin_registry_get_default (),
+ tracker_grl_plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ g_free (source_name);
+ }
+
+ tracker_sparql_cursor_next_async (cursor, NULL,
+ (GAsyncReadyCallback) tracker_get_datasource_cb,
+ cursor);
+}
+
+static void
+tracker_get_datasources_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer data)
+{
+ TrackerSparqlCursor *cursor;
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ cursor = tracker_sparql_connection_query_finish (tracker_connection,
+ result, NULL);
+
+ tracker_sparql_cursor_next_async (cursor, NULL,
+ (GAsyncReadyCallback) tracker_get_datasource_cb,
+ cursor);
+}
+
+static void
+tracker_get_connection_cb (GObject *object,
+ GAsyncResult *res,
+ const GrlPluginInfo *plugin)
+{
+ /* GrlTrackerSource *source; */
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ tracker_connection = tracker_sparql_connection_get_finish (res, NULL);
+
+ if (tracker_connection != NULL) {
+ if (tracker_per_device_source == TRUE) {
+ /* Let's discover available data sources. */
+ GRL_DEBUG ("per device source mode");
+
+ tracker_dbus_start_watch ();
+
+ volume_monitor = g_volume_monitor_get ();
+
+ tracker_sparql_connection_query_async (tracker_connection,
+ TRACKER_DATASOURCES_REQUEST,
+ NULL,
+ (GAsyncReadyCallback) tracker_get_datasources_cb,
+ NULL);
+ } else {
+ /* One source to rule them all. */
+ grl_tracker_add_source (grl_tracker_source_new (tracker_connection));
+ }
+ }
+}
+
+gboolean
+grl_tracker_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GrlConfig *config;
+ gint config_count;
+
+ GRL_LOG_DOMAIN_INIT (tracker_log_domain, "tracker");
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ tracker_grl_plugin = plugin;
+
+ if (!configs) {
+ GRL_WARNING ("Configuration not provided! Using default configuration.");
+ } else {
+ config_count = g_list_length (configs);
+ if (config_count > 1) {
+ GRL_WARNING ("Provided %i configs, but will only use one", config_count);
+ }
+
+ config = GRL_CONFIG (configs->data);
+
+ tracker_per_device_source = grl_config_get_boolean (config,
+ "per-device-source");
+ }
+
+ tracker_sparql_connection_get_async (NULL,
+ (GAsyncReadyCallback) tracker_get_connection_cb,
+ (gpointer) plugin);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_tracker_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Tracker GObject ================ */
+
+static GrlTrackerSource *
+grl_tracker_source_new (TrackerSparqlConnection *connection)
+{
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ return g_object_new (GRL_TRACKER_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ "tracker-connection", connection,
+ NULL);
+}
+
+G_DEFINE_TYPE (GrlTrackerSource, grl_tracker_source, GRL_TYPE_MEDIA_SOURCE);
+
+static void
+grl_tracker_source_class_init (GrlTrackerSourceClass * klass)
+{
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ GObjectClass *g_class = G_OBJECT_CLASS (klass);
+
+ source_class->query = grl_tracker_source_query;
+ source_class->metadata = grl_tracker_source_metadata;
+ source_class->search = grl_tracker_source_search;
+ source_class->browse = grl_tracker_source_browse;
+ source_class->cancel = grl_tracker_source_cancel;
+
+ metadata_class->supported_keys = grl_tracker_source_supported_keys;
+
+ g_class->finalize = grl_tracker_source_finalize;
+ g_class->set_property = grl_tracker_source_set_property;
+ g_class->constructed = grl_tracker_source_constructed;
+
+ g_object_class_install_property (g_class,
+ PROP_TRACKER_CONNECTION,
+ g_param_spec_object ("tracker-connection",
+ "tracker connection",
+ "A Tracker connection",
+ TRACKER_SPARQL_TYPE_CONNECTION,
+ G_PARAM_WRITABLE
+ | G_PARAM_CONSTRUCT_ONLY
+ | G_PARAM_STATIC_NAME));
+
+ g_type_class_add_private (klass, sizeof (GrlTrackerSourcePriv));
+
+ setup_key_mappings ();
+}
+
+static void
+grl_tracker_source_init (GrlTrackerSource *source)
+{
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+
+ source->priv = priv;
+
+ priv->operations = g_hash_table_new (g_direct_hash, g_direct_equal);
+}
+
+static void
+grl_tracker_source_constructed (GObject *object)
+{
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (object);
+
+ if (tracker_per_device_source)
+ g_object_get (object, "source-id", &priv->tracker_datasource, NULL);
+}
+
+static void
+grl_tracker_source_finalize (GObject *object)
+{
+ GrlTrackerSource *self;
+
+ self = GRL_TRACKER_SOURCE (object);
+ if (self->priv->tracker_connection)
+ g_object_unref (self->priv->tracker_connection);
+
+ G_OBJECT_CLASS (grl_tracker_source_parent_class)->finalize (object);
+}
+
+static void
+grl_tracker_source_set_property (GObject *object,
+ guint propid,
+ const GValue *value,
+ GParamSpec *pspec)
+
+{
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (object);
+
+ switch (propid) {
+ case PROP_TRACKER_CONNECTION:
+ if (priv->tracker_connection != NULL)
+ g_object_unref (G_OBJECT (priv->tracker_connection));
+ priv->tracker_connection = g_object_ref (g_value_get_object (value));
+ break;
+
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+}
+
+/* ======================= Utilities ==================== */
+
+static gchar *
+get_tracker_source_name (const gchar *uri, const gchar *datasource)
+{
+ gchar *source_name = NULL;
+ GList *mounts, *mount;
+ GFile *file;
+
+ if (uri != NULL) {
+ mounts = g_volume_monitor_get_mounts (volume_monitor);
+ file = g_file_new_for_uri (uri);
+
+ mount = mounts;
+ while (mount != NULL) {
+ GFile *m_file = g_mount_get_root (G_MOUNT (mount->data));
+
+ if (g_file_equal (m_file, file)) {
+ gchar *m_name = g_mount_get_name (G_MOUNT (mount->data));
+ g_object_unref (G_OBJECT (m_file));
+ source_name = g_strdup_printf ("%s %s", SOURCE_NAME, m_name);
+ g_free (m_name);
+ break;
+ }
+ g_object_unref (G_OBJECT (m_file));
+
+ mount = mount->next;
+ }
+ g_list_foreach (mounts, (GFunc) g_object_unref, NULL);
+ g_list_free (mounts);
+ g_object_unref (G_OBJECT (file));
+
+ if (source_name == NULL)
+ source_name = g_strdup_printf ("%s %s", SOURCE_NAME, datasource);
+ } else {
+ source_name = g_strdup (SOURCE_NAME " Local");
+ }
+
+ return source_name;
+}
+
+static gchar *
+build_flavored_key (gchar *key, const gchar *flavor)
+{
+ gint i = 0;
+
+ while (key[i] != '\0') {
+ if (!g_ascii_isalnum (key[i])) {
+ key[i] = '_';
+ }
+ i++;
+ }
+
+ return g_strdup_printf ("%s_%s", key, flavor);
+}
+
+static void
+insert_key_mapping (GrlKeyID grl_key,
+ const gchar *sparql_key_attr,
+ const gchar *sparql_key_flavor)
+{
+ tracker_grl_sparql_t *assoc = g_slice_new0 (tracker_grl_sparql_t);
+ GList *assoc_list = g_hash_table_lookup (grl_to_sparql_mapping, grl_key);
+ gchar *canon_name = g_strdup (g_param_spec_get_name (grl_key));
+
+ assoc->grl_key = grl_key;
+ assoc->sparql_key_name = build_flavored_key (canon_name, sparql_key_flavor);
+ assoc->sparql_key_attr = sparql_key_attr;
+ assoc->sparql_key_flavor = sparql_key_flavor;
+
+ assoc_list = g_list_append (assoc_list, assoc);
+
+ g_hash_table_insert (grl_to_sparql_mapping, grl_key, assoc_list);
+ g_hash_table_insert (sparql_to_grl_mapping,
+ (gpointer) assoc->sparql_key_name,
+ assoc);
+ g_hash_table_insert (sparql_to_grl_mapping,
+ (gpointer) g_param_spec_get_name (G_PARAM_SPEC (grl_key)),
+ assoc);
+
+ g_free (canon_name);
+}
+
+static void
+setup_key_mappings (void)
+{
+ grl_to_sparql_mapping = g_hash_table_new (g_direct_hash, g_direct_equal);
+ sparql_to_grl_mapping = g_hash_table_new (g_str_hash, g_str_equal);
+
+ insert_key_mapping (GRL_METADATA_KEY_ALBUM,
+ "nmm:albumTitle(nmm:musicAlbum(?urn))",
+ "audio");
+
+ insert_key_mapping (GRL_METADATA_KEY_ARTIST,
+ "nmm:artistName(nmm:performer(?urn))",
+ "audio");
+
+ insert_key_mapping (GRL_METADATA_KEY_AUTHOR,
+ "nmm:artistName(nmm:performer(?urn))",
+ "audio");
+
+ insert_key_mapping (GRL_METADATA_KEY_BITRATE,
+ "nfo:averageBitrate(?urn)",
+ "audio");
+
+ insert_key_mapping (GRL_METADATA_KEY_CHILDCOUNT,
+ "nfo:entryCounter(?urn)",
+ "directory");
+
+ insert_key_mapping (GRL_METADATA_KEY_DATE,
+ "nfo:fileLastModified(?urn)",
+ "file");
+
+ insert_key_mapping (GRL_METADATA_KEY_DURATION,
+ "nfo:duration(?urn)",
+ "audio");
+
+ insert_key_mapping (GRL_METADATA_KEY_FRAMERATE,
+ "nfo:frameRate(?urn)",
+ "video");
+
+ insert_key_mapping (GRL_METADATA_KEY_HEIGHT,
+ "nfo:height(?urn)",
+ "video");
+
+ insert_key_mapping (GRL_METADATA_KEY_ID,
+ "?urn",
+ "file");
+
+ insert_key_mapping (GRL_METADATA_KEY_LAST_PLAYED,
+ "nfo:fileLastAccessed(?urn)",
+ "file");
+
+ insert_key_mapping (GRL_METADATA_KEY_MIME,
+ "nie:mimeType(?urn)",
+ "file");
+
+ insert_key_mapping (GRL_METADATA_KEY_SITE,
+ "nie:url(?urn)",
+ "file");
+
+ insert_key_mapping (GRL_METADATA_KEY_TITLE,
+ "nie:title(?urn)",
+ "audio");
+
+ insert_key_mapping (GRL_METADATA_KEY_TITLE,
+ "nfo:fileName(?urn)",
+ "file");
+
+ insert_key_mapping (GRL_METADATA_KEY_URL,
+ "nie:url(?urn)",
+ "file");
+
+ insert_key_mapping (GRL_METADATA_KEY_WIDTH,
+ "nfo:width(?urn)",
+ "video");
+}
+
+static tracker_grl_sparql_t *
+get_mapping_from_sparql (const gchar *key)
+{
+ return (tracker_grl_sparql_t *) g_hash_table_lookup (sparql_to_grl_mapping,
+ key);
+}
+
+static GList *
+get_mapping_from_grl (const GrlKeyID key)
+{
+ return (GList *) g_hash_table_lookup (grl_to_sparql_mapping, key);
+}
+
+static struct OperationSpec *
+tracker_operation_initiate (GrlMediaSource *source,
+ GrlTrackerSourcePriv *priv,
+ guint operation_id)
+{
+ struct OperationSpec *os = g_slice_new0 (struct OperationSpec);
+
+ os->source = source;
+ os->priv = priv;
+ os->operation_id = operation_id;
+ os->cancel_op = g_cancellable_new ();
+
+ g_hash_table_insert (priv->operations, GSIZE_TO_POINTER (operation_id), os);
+
+ return os;
+}
+
+static void
+tracker_operation_terminate (struct OperationSpec *os)
+{
+ if (os == NULL)
+ return;
+
+ g_hash_table_remove (os->priv->operations,
+ GSIZE_TO_POINTER (os->operation_id));
+
+ g_object_unref (G_OBJECT (os->cursor));
+ g_object_unref (G_OBJECT (os->cancel_op));
+ g_slice_free (struct OperationSpec, os);
+}
+
+static gchar *
+get_select_string (GrlMediaSource *source, const GList *keys)
+{
+ const GList *key = keys;
+ GString *gstr = g_string_new ("");
+ GList *assoc_list;
+ tracker_grl_sparql_t *assoc;
+
+ while (key != NULL) {
+ assoc_list = get_mapping_from_grl ((GrlKeyID) key->data);
+ while (assoc_list != NULL) {
+ assoc = (tracker_grl_sparql_t *) assoc_list->data;
+ if (assoc != NULL) {
+ g_string_append_printf (gstr, "%s AS %s",
+ assoc->sparql_key_attr,
+ assoc->sparql_key_name);
+ g_string_append (gstr, " ");
+ }
+ assoc_list = assoc_list->next;
+ }
+ key = key->next;
+ }
+
+ return g_string_free (gstr, FALSE);
+}
+
+/* Builds an appropriate GrlMedia based on ontology type returned by tracker, or
+ NULL if unknown */
+static GrlMedia *
+build_grilo_media (const gchar *rdf_type)
+{
+ GrlMedia *media = NULL;
+ gchar **rdf_single_type;
+ int i;
+
+ if (!rdf_type) {
+ return NULL;
+ }
+
+ /* As rdf_type can be formed by several types, split them */
+ rdf_single_type = g_strsplit (rdf_type, ",", -1);
+ i = g_strv_length (rdf_single_type) - 1;
+
+ while (!media && i >= 0) {
+ if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_MUSIC)) {
+ media = grl_media_audio_new ();
+ } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_VIDEO)) {
+ media = grl_media_video_new ();
+ } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_IMAGE)) {
+ media = grl_media_image_new ();
+ } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_ARTIST)) {
+ media = grl_media_box_new ();
+ } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_ALBUM)) {
+ media = grl_media_box_new ();
+ } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_BOX)) {
+ media = grl_media_box_new ();
+ }
+ i--;
+ }
+
+ g_strfreev (rdf_single_type);
+
+ return media;
+}
+
+static void
+fill_grilo_media_from_sparql (GrlMedia *media,
+ TrackerSparqlCursor *cursor,
+ gint column)
+{
+ const gchar *sparql_key = tracker_sparql_cursor_get_variable_name (cursor, column);
+ tracker_grl_sparql_t *assoc = get_mapping_from_sparql (sparql_key);;
+ union {
+ gint int_val;
+ gdouble double_val;
+ const gchar *str_val;
+ } val;
+
+ if (assoc == NULL)
+ return;
+
+ GRL_DEBUG ("\tSetting media prop (col=%i/var=%s/prop=%s) %s",
+ column,
+ sparql_key,
+ g_param_spec_get_name (G_PARAM_SPEC (assoc->grl_key)),
+ tracker_sparql_cursor_get_string (cursor, column, NULL));
+
+ if (tracker_sparql_cursor_is_bound (cursor, column) == FALSE) {
+ GRL_DEBUG ("\t\tDropping, no data");
+ return;
+ }
+
+ if (grl_data_has_key (GRL_DATA (media), assoc->grl_key)) {
+ GRL_DEBUG ("\t\tDropping, already here");
+ return;
+ }
+
+ switch (G_PARAM_SPEC (assoc->grl_key)->value_type) {
+ case G_TYPE_STRING:
+ val.str_val = tracker_sparql_cursor_get_string (cursor, column, NULL);
+ if (val.str_val != NULL)
+ grl_data_set_string (GRL_DATA (media), assoc->grl_key, val.str_val);
+ break;
+
+ case G_TYPE_INT:
+ val.int_val = tracker_sparql_cursor_get_integer (cursor, column);
+ grl_data_set_int (GRL_DATA (media), assoc->grl_key, val.int_val);
+ break;
+
+ case G_TYPE_FLOAT:
+ val.double_val = tracker_sparql_cursor_get_double (cursor, column);
+ grl_data_set_float (GRL_DATA (media), assoc->grl_key, (gfloat) val.double_val);
+ break;
+
+ default:
+ GRL_DEBUG ("\t\tUnexpected data type");
+ break;
+ }
+}
+
+static void
+tracker_query_result_cb (GObject *source_object,
+ GAsyncResult *result,
+ struct OperationSpec *operation)
+{
+ gint col;
+ const gchar *sparql_type;
+ GError *tracker_error = NULL, *error = NULL;
+ GrlMedia *media;
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ if (g_cancellable_is_cancelled (operation->cancel_op)) {
+ GRL_DEBUG ("\tOperation %u cancelled", operation->operation_id);
+ operation->callback (operation->source,
+ operation->operation_id,
+ NULL, 0,
+ operation->user_data, NULL);
+ tracker_operation_terminate (operation);
+
+ return;
+ }
+
+ if (!tracker_sparql_cursor_next_finish (operation->cursor,
+ result,
+ &tracker_error)) {
+ if (tracker_error != NULL) {
+ GRL_DEBUG ("\terror in parsing : %s", tracker_error->message);
+
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Failed to start browse action : %s",
+ tracker_error->message);
+
+ operation->callback (operation->source,
+ operation->operation_id,
+ NULL, 0,
+ operation->user_data, error);
+
+ g_error_free (error);
+ g_error_free (tracker_error);
+ } else {
+ GRL_DEBUG ("\tend of parsing :)");
+
+ /* Only emit this last one if more result than expected */
+ if (operation->count > 1)
+ operation->callback (operation->source,
+ operation->operation_id,
+ NULL, 0,
+ operation->user_data, NULL);
+ }
+
+ tracker_operation_terminate (operation);
+ return;
+ }
+
+ sparql_type = tracker_sparql_cursor_get_string (operation->cursor, 0, NULL);
+
+ GRL_DEBUG ("Parsing line %i of type %s", operation->current, sparql_type);
+
+ media = build_grilo_media (sparql_type);
+
+ if (media != NULL) {
+ for (col = 1 ;
+ col < tracker_sparql_cursor_get_n_columns (operation->cursor) ;
+ col++) {
+ fill_grilo_media_from_sparql (media, operation->cursor, col);
+ }
+
+ operation->callback (operation->source,
+ operation->operation_id,
+ media,
+ --operation->count,
+ operation->user_data,
+ NULL);
+ }
+
+ /* Schedule the next line to parse */
+ operation->current++;
+ if (operation->count < 1)
+ tracker_operation_terminate (operation);
+ else
+ tracker_sparql_cursor_next_async (operation->cursor, operation->cancel_op,
+ (GAsyncReadyCallback) tracker_query_result_cb,
+ (gpointer) operation);
+}
+
+static void
+tracker_query_cb (GObject *source_object,
+ GAsyncResult *result,
+ struct OperationSpec *operation)
+{
+ GError *tracker_error = NULL, *error = NULL;
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ operation->cursor =
+ tracker_sparql_connection_query_finish (operation->priv->tracker_connection,
+ result, &tracker_error);
+
+ if (tracker_error) {
+ GRL_WARNING ("Could not execute sparql query: %s", tracker_error->message);
+
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Failed to start browse action : %s",
+ tracker_error->message);
+
+ operation->callback (operation->source, operation->operation_id, NULL, 0,
+ operation->user_data, error);
+
+ g_error_free (tracker_error);
+ g_error_free (error);
+ g_slice_free (struct OperationSpec, operation);
+
+ return;
+ }
+
+ /* Start parsing results */
+ operation->current = 0;
+ tracker_sparql_cursor_next_async (operation->cursor, NULL,
+ (GAsyncReadyCallback) tracker_query_result_cb,
+ (gpointer) operation);
+}
+
+static void
+tracker_metadata_cb (GObject *source_object,
+ GAsyncResult *result,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (ms->source);
+ gint col;
+ GError *tracker_error = NULL, *error = NULL;
+ TrackerSparqlCursor *cursor;
+
+ GRL_DEBUG ("%s", __FUNCTION__);
+
+ cursor = tracker_sparql_connection_query_finish (priv->tracker_connection,
+ result, &tracker_error);
+
+ if (tracker_error) {
+ GRL_WARNING ("Could not execute sparql query: %s", tracker_error->message);
+
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Failed to start browse action : %s",
+ tracker_error->message);
+
+ ms->callback (ms->source, NULL, ms->user_data, error);
+
+ g_error_free (tracker_error);
+ g_error_free (error);
+
+ goto end_operation;
+ }
+
+
+ tracker_sparql_cursor_next (cursor, NULL, NULL);
+
+ /* Translate Sparql result into Grilo result */
+ for (col = 0 ; col < tracker_sparql_cursor_get_n_columns (cursor) ; col++) {
+ fill_grilo_media_from_sparql (ms->media, cursor, col);
+ }
+
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+
+ end_operation:
+ if (cursor)
+ g_object_unref (G_OBJECT (cursor));
+}
+
+static gchar *
+tracker_source_get_device_constraint (GrlTrackerSourcePriv *priv)
+{
+ if (priv->tracker_datasource == NULL)
+ return g_strdup ("");
+
+ return g_strdup_printf ("?urn nie:dataSource <%s> .",
+ priv->tracker_datasource);
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_tracker_source_supported_keys (GrlMetadataSource *source)
+{
+ return grl_plugin_registry_get_metadata_keys (grl_plugin_registry_get_default ());
+}
+
+/**
+ * Query is a SPARQL query.
+ *
+ * Columns must be named with the Grilo key name that the column
+ * represent. Unnamed or unknown columns will be ignored.
+ *
+ * First column must be the media type, and it does not need to be named. It
+ * must match with any value supported in rdf:type() property, or
+ * grilo#Box. Types understood are:
+ *
+ * <itemizedlist>
+ * <listitem>
+ * <para>
+ * <literal>nmm#MusicPiece</literal>
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * <literal>nmm#Video</literal>
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * <literal>nmm#Photo</literal>
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * <literal>nmm#Artist</literal>
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * <literal>nmm#MusicAlbum</literal>
+ * </para>
+ * </listitem>
+ * <listitem>
+ * <para>
+ * <literal>grilo#Box</literal>
+ * </para>
+ * </listitem>
+ * </itemizedlist>
+ *
+ * An example for searching all songs:
+ *
+ * <informalexample>
+ * <programlisting>
+ * SELECT rdf:type(?song)
+ * ?song AS id
+ * nie:title(?song) AS title
+ * nie:url(?song) AS url
+ * WHERE { ?song a nmm:MusicPiece }
+ * </programlisting>
+ * </informalexample>
+ *
+ * Alternatively, we can use a partial SPARQL query: just specify the sentence
+ * in the WHERE part. In this case, "?urn" is the ontology concept to be used in
+ * the clause.
+ *
+ * An example of such partial query:
+ *
+ * <informalexample>
+ * <programlisting>
+ * ?urn a nfo:Media
+ * </programlisting>
+ * </informalexample>
+ *
+ * In this case, all data required to build a full SPARQL query will be get from
+ * the query spec.
+ */
+static void
+grl_tracker_source_query (GrlMediaSource *source,
+ GrlMediaSourceQuerySpec *qs)
+{
+ GError *error = NULL;
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+ gchar *constraint;
+ gchar *sparql_final;
+ gchar *sparql_select;
+ struct OperationSpec *os;
+
+ GRL_DEBUG ("%s: id=%u", __FUNCTION__, qs->query_id);
+
+ if (!qs->query || qs->query[0] == '\0') {
+ error = g_error_new_literal (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_QUERY_FAILED,
+ "Empty query");
+ goto send_error;
+ }
+
+ /* Check if it is a full sparql query */
+ if (g_ascii_strncasecmp (qs->query, "select ", 7) != 0) {
+ constraint = tracker_source_get_device_constraint (priv);
+ sparql_select = get_select_string (source, qs->keys);
+ sparql_final = g_strdup_printf (TRACKER_QUERY_REQUEST,
+ sparql_select,
+ qs->query,
+ constraint,
+ qs->skip,
+ qs->count);
+ g_free (constraint);
+ g_free (qs->query);
+ g_free (sparql_select);
+ qs->query = sparql_final;
+ grl_tracker_source_query (source, qs);
+ return;
+ }
+
+ GRL_DEBUG ("select : %s", qs->query);
+
+ os = tracker_operation_initiate (source, priv, qs->query_id);
+ os->keys = qs->keys;
+ os->skip = qs->skip;
+ os->count = qs->count;
+ os->callback = qs->callback;
+ os->user_data = qs->user_data;
+
+ tracker_sparql_connection_query_async (priv->tracker_connection,
+ qs->query,
+ os->cancel_op,
+ (GAsyncReadyCallback) tracker_query_cb,
+ os);
+
+ return;
+
+ send_error:
+ qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
+ g_error_free (error);
+}
+
+static void
+grl_tracker_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+ gchar *sparql_select, *sparql_final;
+
+ GRL_DEBUG ("%s: id=%i", __FUNCTION__, ms->metadata_id);
+
+ sparql_select = get_select_string (source, ms->keys);
+ sparql_final = g_strdup_printf (TRACKER_METADATA_REQUEST, sparql_select,
+ grl_media_get_id (ms->media));
+
+ GRL_DEBUG ("select: '%s'", sparql_final);
+
+ tracker_sparql_connection_query_async (priv->tracker_connection,
+ sparql_final,
+ NULL,
+ (GAsyncReadyCallback) tracker_metadata_cb,
+ ms);
+
+ if (sparql_select != NULL)
+ g_free (sparql_select);
+ if (sparql_final != NULL)
+ g_free (sparql_final);
+}
+
+static void
+grl_tracker_source_search (GrlMediaSource *source, GrlMediaSourceSearchSpec *ss)
+{
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+ gchar *constraint;
+ gchar *sparql_select;
+ gchar *sparql_final;
+ struct OperationSpec *os;
+
+ GRL_DEBUG ("%s: id=%u", __FUNCTION__, ss->search_id);
+
+ constraint = tracker_source_get_device_constraint (priv);
+ sparql_select = get_select_string (source, ss->keys);
+ if (!ss->text || ss->text[0] == '\0') {
+ /* Search all */
+ sparql_final = g_strdup_printf (TRACKER_SEARCH_ALL_REQUEST, sparql_select,
+ constraint, ss->skip, ss->count);
+ } else {
+ sparql_final = g_strdup_printf (TRACKER_SEARCH_REQUEST, sparql_select,
+ ss->text, constraint, ss->skip, ss->count);
+ }
+
+ GRL_DEBUG ("select: '%s'", sparql_final);
+
+ os = tracker_operation_initiate (source, priv, ss->search_id);
+ os->keys = ss->keys;
+ os->skip = ss->skip;
+ os->count = ss->count;
+ os->callback = ss->callback;
+ os->user_data = ss->user_data;
+
+ tracker_sparql_connection_query_async (priv->tracker_connection,
+ sparql_final,
+ os->cancel_op,
+ (GAsyncReadyCallback) tracker_query_cb,
+ os);
+
+ g_free (constraint);
+ g_free (sparql_select);
+ g_free (sparql_final);
+}
+
+static void
+grl_tracker_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+ gchar *constraint;
+ gchar *sparql_select;
+ gchar *sparql_final;
+ struct OperationSpec *os;
+ GrlMedia *media;
+
+ GRL_DEBUG ("%s: id=%u", __FUNCTION__, bs->browse_id);
+
+ if ((bs->container == NULL || grl_media_get_id (bs->container) == NULL)) {
+ /* Hardcoded categories */
+ media = grl_media_box_new ();
+ grl_media_set_title (media, "Music");
+ grl_media_set_id (media, "nmm:MusicPiece");
+ bs->callback (bs->source, bs->browse_id, media, 2, bs->user_data, NULL);
+
+ media = grl_media_box_new ();
+ grl_media_set_title (media, "Photo");
+ grl_media_set_id (media, "nmm:Photo");
+ bs->callback (bs->source, bs->browse_id, media, 1, bs->user_data, NULL);
+
+ media = grl_media_box_new ();
+ grl_media_set_title (media, "Video");
+ grl_media_set_id (media, "nmm:Video");
+ bs->callback (bs->source, bs->browse_id, media, 0, bs->user_data, NULL);
+ return;
+ }
+
+ constraint = tracker_source_get_device_constraint (priv);
+ sparql_select = get_select_string (bs->source, bs->keys);
+ sparql_final = g_strdup_printf (TRACKER_BROWSE_CATEGORY_REQUEST,
+ sparql_select,
+ grl_media_get_id (bs->container),
+ constraint,
+ bs->skip, bs->count);
+
+ GRL_DEBUG ("select: '%s'", sparql_final);
+
+ os = tracker_operation_initiate (source, priv, bs->browse_id);
+ os->keys = bs->keys;
+ os->skip = bs->skip;
+ os->count = bs->count;
+ os->callback = bs->callback;
+ os->user_data = bs->user_data;
+
+ tracker_sparql_connection_query_async (priv->tracker_connection,
+ sparql_final,
+ os->cancel_op,
+ (GAsyncReadyCallback) tracker_query_cb,
+ os);
+
+ g_free (constraint);
+ g_free (sparql_select);
+ g_free (sparql_final);
+}
+
+static void
+grl_tracker_source_cancel (GrlMediaSource *source, guint operation_id)
+{
+ GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
+ struct OperationSpec *os;
+
+ GRL_DEBUG ("%s: id=%u", __FUNCTION__, operation_id);
+
+ os = g_hash_table_lookup (priv->operations, GSIZE_TO_POINTER (operation_id));
+
+ if (os != NULL)
+ g_cancellable_cancel (os->cancel_op);
+}
diff --git a/src/media/tracker/grl-tracker.h b/src/media/tracker/grl-tracker.h
new file mode 100644
index 0000000..ddf46ce
--- /dev/null
+++ b/src/media/tracker/grl-tracker.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2011 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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_TRACKER_H_
+#define _GRL_TRACKER_H_
+
+#include <grilo.h>
+
+#define GRL_TRACKER_SOURCE_TYPE \
+ (grl_tracker_source_get_type ())
+
+#define GRL_TRACKER_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_TRACKER_SOURCE_TYPE, \
+ GrlTrackerSource))
+
+#define GRL_IS_TRACKER_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_TRACKER_SOURCE_TYPE))
+
+#define GRL_TRACKER_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_TRACKER_SOURCE_TYPE, \
+ GrlTrackerSourceClass))
+
+#define GRL_IS_TRACKER_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_TRACKER_SOURCE_TYPE))
+
+#define GRL_TRACKER_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_TRACKER_SOURCE_TYPE, \
+ GrlTrackerSourceClass))
+
+typedef struct _GrlTrackerSource GrlTrackerSource;
+typedef struct _GrlTrackerSourcePriv GrlTrackerSourcePriv;
+
+struct _GrlTrackerSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlTrackerSourcePriv *priv;
+
+};
+
+typedef struct _GrlTrackerSourceClass GrlTrackerSourceClass;
+
+struct _GrlTrackerSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_tracker_source_get_type (void);
+
+#endif /* _GRL_TRACKER_H_ */
diff --git a/src/media/tracker/grl-tracker.xml b/src/media/tracker/grl-tracker.xml
new file mode 100644
index 0000000..20f7bb6
--- /dev/null
+++ b/src/media/tracker/grl-tracker.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Tracker</name>
+ <description>A plugin for searching multimedia content using Tracker</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/upnp/Makefile.am b/src/media/upnp/Makefile.am
new file mode 100644
index 0000000..b6b09bd
--- /dev/null
+++ b/src/media/upnp/Makefile.am
@@ -0,0 +1,46 @@
+#
+# Makefile.am
+#
+# Author: Iago Toral Quiroga <itoral igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlupnp.la
+
+libgrlupnp_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GUPNP_CFLAGS) \
+ $(GUPNPAV_CFLAGS) \
+ $(GTHREAD_CFLAGS) \
+ $(XML_CFLAGS)
+
+libgrlupnp_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GUPNP_LIBS) \
+ $(GUPNPAV_LIBS) \
+ $(GTHREAD_LIBS) \
+ $(XML_LIBS)
+
+libgrlupnp_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlupnp_la_CFLAGS += \
+ $(XML_CFLAGS)
+
+libgrlupnp_la_LIBADD += \
+ $(XML_LIBS)
+
+libgrlupnp_la_SOURCES = grl-upnp.c grl-upnp.h
+
+libdir = $(GRL_PLUGINS_DIR)
+upnpxmldir = $(GRL_PLUGINS_CONF_DIR)
+upnpxml_DATA = $(UPNP_PLUGIN_ID).xml
+
+EXTRA_DIST = $(upnpxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/upnp/grl-upnp.c b/src/media/upnp/grl-upnp.c
new file mode 100644
index 0000000..f024b53
--- /dev/null
+++ b/src/media/upnp/grl-upnp.c
@@ -0,0 +1,1356 @@
+/*
+ * Copyright (C) 2010, 2011 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * This component is based on Maemo's mafw-upnp-source source code.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
+#include <libgupnp/gupnp.h>
+#include <libgupnp-av/gupnp-av.h>
+#include <string.h>
+#include <stdlib.h>
+#include <libxml/tree.h>
+
+#include "grl-upnp.h"
+
+#define GRL_UPNP_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), GRL_UPNP_SOURCE_TYPE, GrlUpnpPrivate))
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT upnp_log_domain
+GRL_LOG_DOMAIN_STATIC(upnp_log_domain);
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID UPNP_PLUGIN_ID
+
+#define SOURCE_ID_TEMPLATE "grl-upnp-%s"
+#define SOURCE_NAME_TEMPLATE "UPnP - %s"
+#define SOURCE_DESC_TEMPLATE "A source for browsing the UPnP server '%s'"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+/* --- Other --- */
+
+#ifndef CONTENT_DIR_SERVICE
+#define CONTENT_DIR_SERVICE "urn:schemas-upnp-org:service:ContentDirectory"
+#endif
+
+#define UPNP_SEARCH_SPEC \
+ "dc:title contains \"%s\" or " \
+ "upnp:album contains \"%s\" or " \
+ "upnp:artist contains \"%s\""
+
+#define UPNP_SEARCH_ALL \
+ "upnp:class derivedfrom \"object.item\""
+
+struct _GrlUpnpPrivate {
+ GUPnPDeviceProxy* device;
+ GUPnPServiceProxy* service;
+ GUPnPControlPoint *cp;
+ gboolean search_enabled;
+ gchar *upnp_name;
+};
+
+struct OperationSpec {
+ GrlMediaSource *source;
+ guint operation_id;
+ GList *keys;
+ guint skip;
+ guint count;
+ GrlMediaSourceResultCb callback;
+ gpointer user_data;
+};
+
+struct SourceInfo {
+ gchar *source_id;
+ gchar *source_name;
+ GUPnPDeviceProxy* device;
+ GUPnPServiceProxy* service;
+ GrlPluginInfo *plugin;
+};
+
+static void setup_key_mappings (void);
+
+static gchar *build_source_id (const gchar *udn);
+
+static GrlUpnpSource *grl_upnp_source_new (const gchar *id, const gchar *name);
+
+gboolean grl_upnp_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+static void grl_upnp_source_finalize (GObject *plugin);
+
+static const GList *grl_upnp_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_upnp_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+
+static void grl_upnp_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+
+static void grl_upnp_source_query (GrlMediaSource *source,
+ GrlMediaSourceQuerySpec *qs);
+
+static void grl_upnp_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms);
+
+static GrlSupportedOps grl_upnp_source_supported_operations (GrlMetadataSource *source);
+
+static gboolean grl_upnp_source_notify_change_start (GrlMediaSource *source,
+ GError **error);
+
+static gboolean grl_upnp_source_notify_change_stop (GrlMediaSource *source,
+ GError **error);
+
+static void context_available_cb (GUPnPContextManager *context_manager,
+ GUPnPContext *context,
+ gpointer user_data);
+static void device_available_cb (GUPnPControlPoint *cp,
+ GUPnPDeviceProxy *device,
+ gpointer user_data);
+static void device_unavailable_cb (GUPnPControlPoint *cp,
+ GUPnPDeviceProxy *device,
+ gpointer user_data);
+
+/* ===================== Globals ================= */
+
+static GHashTable *key_mapping = NULL;
+static GHashTable *filter_key_mapping = NULL;
+static GUPnPContextManager *context_manager = NULL;
+
+/* =================== UPnP Plugin =============== */
+
+gboolean
+grl_upnp_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (upnp_log_domain, "upnp");
+
+ GRL_DEBUG ("grl_upnp_plugin_init");
+
+ /* libsoup needs this */
+ if (!g_thread_supported()) {
+ g_thread_init (NULL);
+ }
+
+ context_manager = gupnp_context_manager_new (NULL, 0);
+ g_signal_connect (context_manager,
+ "context-available",
+ G_CALLBACK (context_available_cb),
+ (gpointer)plugin);
+
+ return TRUE;
+}
+
+static void
+grl_upnp_plugin_deinit (void)
+{
+ GRL_DEBUG ("grl_upnp_plugin_deinit");
+
+ if (context_manager != NULL) {
+ g_object_unref (context_manager);
+ context_manager = NULL;
+ }
+}
+
+GRL_PLUGIN_REGISTER (grl_upnp_plugin_init,
+ grl_upnp_plugin_deinit,
+ PLUGIN_ID);
+
+/* ================== UPnP GObject ================ */
+
+G_DEFINE_TYPE (GrlUpnpSource, grl_upnp_source, GRL_TYPE_MEDIA_SOURCE);
+
+static GrlUpnpSource *
+grl_upnp_source_new (const gchar *source_id, const gchar *name)
+{
+ gchar *source_name, *source_desc;
+ GrlUpnpSource *source;
+
+ GRL_DEBUG ("grl_upnp_source_new");
+ source_name = g_strdup_printf (SOURCE_NAME_TEMPLATE, name);
+ source_desc = g_strdup_printf (SOURCE_DESC_TEMPLATE, name);
+
+ source = g_object_new (GRL_UPNP_SOURCE_TYPE,
+ "source-id", source_id,
+ "source-name", source_name,
+ "source-desc", source_desc,
+ NULL);
+
+ source->priv->upnp_name = g_strdup (name);
+
+ g_free (source_name);
+ g_free (source_desc);
+
+ return source;
+}
+
+static void
+grl_upnp_source_class_init (GrlUpnpSourceClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+
+ gobject_class->finalize = grl_upnp_source_finalize;
+
+ metadata_class->supported_keys = grl_upnp_source_supported_keys;
+ metadata_class->supported_operations = grl_upnp_source_supported_operations;
+
+ source_class->browse = grl_upnp_source_browse;
+ source_class->search = grl_upnp_source_search;
+ source_class->query = grl_upnp_source_query;
+ source_class->metadata = grl_upnp_source_metadata;
+ source_class->notify_change_start = grl_upnp_source_notify_change_start;
+ source_class->notify_change_stop = grl_upnp_source_notify_change_stop;
+
+ g_type_class_add_private (klass, sizeof (GrlUpnpPrivate));
+
+ setup_key_mappings ();
+}
+
+static void
+grl_upnp_source_init (GrlUpnpSource *source)
+{
+ source->priv = GRL_UPNP_GET_PRIVATE (source);
+}
+
+static void
+grl_upnp_source_finalize (GObject *object)
+{
+ GrlUpnpSource *source;
+
+ GRL_DEBUG ("grl_upnp_source_finalize");
+
+ source = GRL_UPNP_SOURCE (object);
+
+ g_object_unref (source->priv->device);
+ g_object_unref (source->priv->service);
+ g_free (source->priv->upnp_name);
+
+ G_OBJECT_CLASS (grl_upnp_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static gchar *
+build_source_id (const gchar *udn)
+{
+ return g_strdup_printf (SOURCE_ID_TEMPLATE, udn);
+}
+
+static void
+free_source_info (struct SourceInfo *info)
+{
+ g_free (info->source_id);
+ g_free (info->source_name);
+ g_object_unref (info->device);
+ g_object_unref (info->service);
+ g_slice_free (struct SourceInfo, info);
+}
+
+static void
+container_changed_cb (GUPnPServiceProxy *proxy,
+ const char *variable,
+ GValue *value,
+ gpointer user_data)
+{
+ GrlMedia *container;
+ GrlMediaSource *source = GRL_MEDIA_SOURCE (user_data);
+ gchar **tokens;
+ gint i = 0;
+
+ GRL_DEBUG (__func__);
+
+ /* Value is a list of pairs (id, number), where "id" is the container id */
+ tokens = g_strsplit (g_value_get_string (value), ",", -1);
+ while (tokens[i]) {
+ container = grl_media_box_new ();
+ grl_media_set_id (container, tokens[i]);
+ grl_media_source_notify_change (source,
+ container,
+ GRL_CONTENT_CHANGED,
+ FALSE);
+ g_object_unref (container);
+ i += 2;
+ }
+ g_strfreev (tokens);
+}
+
+static void
+gupnp_search_caps_cb (GUPnPServiceProxy *service,
+ GUPnPServiceProxyAction *action,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ gchar *caps = NULL;
+ gchar *name;
+ GrlUpnpSource *source;
+ gchar *source_id;
+ GrlPluginRegistry *registry;
+ struct SourceInfo *source_info;
+ gboolean result;
+
+ result =
+ gupnp_service_proxy_end_action (service, action, &error,
+ "SearchCaps", G_TYPE_STRING, &caps,
+ NULL);
+ if (!result) {
+ GRL_WARNING ("Failed to execute GetSearchCaps operation");
+ if (error) {
+ GRL_WARNING ("Reason: %s", error->message);
+ g_error_free (error);
+ }
+ }
+
+ source_info = (struct SourceInfo *) user_data;
+ name = source_info->source_name;
+ source_id = source_info->source_id;
+
+ registry = grl_plugin_registry_get_default ();
+ if (grl_plugin_registry_lookup_source (registry, source_id)) {
+ GRL_DEBUG ("A source with id '%s' is already registered. Skipping...",
+ source_id);
+ goto free_resources;
+ }
+
+ source = grl_upnp_source_new (source_id, name);
+ source->priv->device = g_object_ref (source_info->device);
+ source->priv->service = g_object_ref (source_info->service);
+
+ GRL_DEBUG ("Search caps for source '%s': '%s'", name, caps);
+
+ if (caps && caps[0] != '\0') {
+ GRL_DEBUG ("Setting search enabled for source '%s'", name );
+ source->priv->search_enabled = TRUE;
+ } else {
+ GRL_DEBUG ("Setting search disabled for source '%s'", name );
+ }
+
+ grl_plugin_registry_register_source (registry,
+ source_info->plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+
+ free_resources:
+ free_source_info (source_info);
+}
+
+static void
+context_available_cb (GUPnPContextManager *context_manager,
+ GUPnPContext *context,
+ gpointer user_data)
+{
+ GUPnPControlPoint *cp;
+
+ GRL_DEBUG ("%s", __func__);
+
+ cp = gupnp_control_point_new (context, "urn:schemas-upnp-org:device:MediaServer:1");
+ g_signal_connect (cp,
+ "device-proxy-available",
+ G_CALLBACK (device_available_cb),
+ user_data);
+ g_signal_connect (cp,
+ "device-proxy-unavailable",
+ G_CALLBACK (device_unavailable_cb),
+ NULL);
+
+ gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
+
+ /* Let context manager take care of the control point life cycle */
+ gupnp_context_manager_manage_control_point (context_manager, cp);
+ g_object_unref (cp);
+}
+
+static void
+device_available_cb (GUPnPControlPoint *cp,
+ GUPnPDeviceProxy *device,
+ gpointer user_data)
+{
+ gchar* name;
+ const gchar* udn;
+ const char *type;
+ GUPnPServiceInfo *service;
+ GrlPluginRegistry *registry;
+ gchar *source_id;
+
+ GRL_DEBUG ("device_available_cb");
+
+ type = gupnp_device_info_get_device_type (GUPNP_DEVICE_INFO (device));
+ GRL_DEBUG (" type: %s", type);
+
+ service = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (device),
+ CONTENT_DIR_SERVICE);
+ if (!service) {
+ GRL_DEBUG ("Device does not provide required service, ignoring...");
+ return;
+ }
+
+ udn = gupnp_device_info_get_udn (GUPNP_DEVICE_INFO (device));
+ GRL_DEBUG (" udn: %s ", udn);
+
+ name = gupnp_device_info_get_friendly_name (GUPNP_DEVICE_INFO (device));
+ GRL_DEBUG (" name: %s", name);
+
+ registry = grl_plugin_registry_get_default ();
+ source_id = build_source_id (udn);
+ if (grl_plugin_registry_lookup_source (registry, source_id)) {
+ GRL_DEBUG ("A source with id '%s' is already registered. Skipping...",
+ source_id);
+ goto free_resources;
+ }
+
+ /* We got a valid UPnP source */
+ /* Now let's check if it supports search operations before registering */
+ struct SourceInfo *source_info = g_slice_new0 (struct SourceInfo);
+ source_info->source_id = g_strdup (source_id);
+ source_info->source_name = g_strdup (name);
+ source_info->device = g_object_ref (device);
+ source_info->service = g_object_ref (service);
+ source_info->plugin = (GrlPluginInfo *) user_data;
+
+ if (!gupnp_service_proxy_begin_action (GUPNP_SERVICE_PROXY (service),
+ "GetSearchCapabilities",
+ gupnp_search_caps_cb,
+ source_info,
+ NULL)) {
+ GrlUpnpSource *source = grl_upnp_source_new (source_id, name);
+ GRL_WARNING ("Failed to start GetCapabilitiesSearch action");
+ GRL_DEBUG ("Setting search disabled for source '%s'", name );
+ registry = grl_plugin_registry_get_default ();
+ grl_plugin_registry_register_source (registry,
+ source_info->plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ free_source_info (source_info);
+ }
+
+ free_resources:
+ g_object_unref (service);
+ g_free (source_id);
+}
+
+static void
+device_unavailable_cb (GUPnPControlPoint *cp,
+ GUPnPDeviceProxy *device,
+ gpointer user_data)
+{
+ const gchar* udn;
+ GrlMediaPlugin *source;
+ GrlPluginRegistry *registry;
+ gchar *source_id;
+
+ GRL_DEBUG ("device_unavailable_cb");
+
+ udn = gupnp_device_info_get_udn (GUPNP_DEVICE_INFO (device));
+ GRL_DEBUG (" udn: %s ", udn);
+
+ registry = grl_plugin_registry_get_default ();
+ source_id = build_source_id (udn);
+ source = grl_plugin_registry_lookup_source (registry, source_id);
+ if (!source) {
+ GRL_DEBUG ("No source registered with id '%s', ignoring", source_id);
+ } else {
+ grl_plugin_registry_unregister_source (registry, source, NULL);
+ }
+
+ g_free (source_id);
+}
+
+const static gchar *
+get_upnp_key (const GrlKeyID key_id)
+{
+ return g_hash_table_lookup (key_mapping, key_id);
+}
+
+const static gchar *
+get_upnp_key_for_filter (const GrlKeyID key_id)
+{
+ return g_hash_table_lookup (filter_key_mapping, key_id);
+}
+
+static gchar *
+get_upnp_filter (const GList *keys)
+{
+ GString *filter;
+ GList *iter;
+ gchar *upnp_key;
+ guint first = TRUE;
+
+ filter = g_string_new ("");
+ iter = (GList *) keys;
+ while (iter) {
+ upnp_key =
+ (gchar *) get_upnp_key_for_filter (iter->data);
+ if (upnp_key) {
+ if (!first) {
+ g_string_append (filter, ",");
+ }
+ g_string_append (filter, upnp_key);
+ first = FALSE;
+ }
+ iter = g_list_next (iter);
+ }
+
+ return g_string_free (filter, FALSE);
+}
+
+static gchar *
+get_upnp_search (const gchar *text)
+{
+ if (text) {
+ return g_strdup_printf (UPNP_SEARCH_SPEC, text, text, text);
+ } else {
+ return g_strdup (UPNP_SEARCH_ALL);
+ }
+}
+
+static void
+setup_key_mappings (void)
+{
+ /* For key_mapping we only have to set mapping for keys that
+ are not handled directly with the corresponding fw key
+ (see ket_valur_for_key) */
+ key_mapping = g_hash_table_new (g_direct_hash, g_direct_equal);
+ filter_key_mapping = g_hash_table_new (g_direct_hash, g_direct_equal);
+
+ g_hash_table_insert (key_mapping, GRL_METADATA_KEY_TITLE, "title");
+ g_hash_table_insert (key_mapping, GRL_METADATA_KEY_ARTIST, "artist");
+ g_hash_table_insert (key_mapping, GRL_METADATA_KEY_ALBUM, "album");
+ g_hash_table_insert (key_mapping, GRL_METADATA_KEY_GENRE, "genre");
+ g_hash_table_insert (key_mapping, GRL_METADATA_KEY_URL, "res");
+ g_hash_table_insert (key_mapping, GRL_METADATA_KEY_DATE, "modified");
+ g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_TITLE, "title");
+ g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_URL, "res");
+ g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_DATE, "modified");
+ g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_ARTIST, "upnp:artist");
+ g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_ALBUM, "upnp:album");
+ g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_GENRE, "upnp:genre");
+ g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_DURATION, "res@duration");
+ g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_DATE, "modified");
+}
+
+static gchar *
+didl_res_get_protocol_info (xmlNode* res_node, gint field)
+{
+ gchar* pinfo;
+ gchar* value;
+ gchar** array;
+
+ pinfo = (gchar *) xmlGetProp (res_node, (const xmlChar *) "protocolInfo");
+ if (pinfo == NULL) {
+ return NULL;
+ }
+
+ /* 0:protocol, 1:network, 2:mime-type and 3:additional info. */
+ array = g_strsplit (pinfo, ":", 4);
+ g_free(pinfo);
+ if (g_strv_length (array) < 4) {
+ value = NULL;
+ } else {
+ value = g_strdup (array[field]);
+ }
+
+ g_strfreev (array);
+
+ return value;
+}
+
+static GList *
+didl_get_supported_resources (GUPnPDIDLLiteObject *didl)
+{
+ GList *properties, *node;
+ xmlNode *xml_node;
+ gchar *protocol;
+
+ properties = gupnp_didl_lite_object_get_properties (didl, "res");
+
+ node = properties;
+ while (node) {
+ xml_node = (xmlNode *) node->data;
+ if (!xml_node) {
+ node = properties = g_list_delete_link (properties, node);
+ continue;
+ }
+
+ protocol = didl_res_get_protocol_info (xml_node, 0);
+ if (protocol && strcmp (protocol, "http-get") != 0) {
+ node = properties = g_list_delete_link (properties, node);
+ g_free (protocol);
+ continue;
+ }
+ g_free (protocol);
+ node = g_list_next (node);
+ }
+
+ return properties;
+}
+
+static gint
+didl_h_mm_ss_to_int (const gchar *time)
+{
+ guint len = 0;
+ guint i = 0;
+ guint head = 0;
+ guint tail = 0;
+ int result = 0;
+ gchar* tmp = NULL;
+ gboolean has_hours = FALSE;
+
+ if (!time) {
+ return -1;
+ }
+
+ len = strlen (time);
+ tmp = g_new0 (gchar, sizeof (gchar) * len);
+
+ /* Find the first colon (it can be anywhere) and also count the
+ * amount of colons to know if there are hours or not */
+ for (i = 0; i < len; i++) {
+ if (time[i] == ':') {
+ if (tail != 0) {
+ has_hours = TRUE;
+ } else {
+ tail = i;
+ }
+ }
+ }
+
+ if (tail > len || head > tail) {
+ g_free (tmp);
+ return -1;
+ }
+
+ /* Hours */
+ if (has_hours == TRUE) {
+ memcpy (tmp, time + head, tail - head);
+ tmp[tail - head + 1] = '\0';
+ result += 3600 * atoi (tmp);
+ /* The next colon should be exactly 2 chars right */
+ head = tail + 1;
+ tail = head + 2;
+ } else {
+ /* The format is now exactly MM:SS */
+ head = 0;
+ tail = 2;
+ }
+
+ /* Bail out if tail goes too far or head is bigger than tail */
+ if (tail > len || head > tail) {
+ g_free (tmp);
+ return -1;
+ }
+
+ /* Minutes */
+ memcpy (tmp, time + head, tail - head);
+ tmp[2] = '\0';
+ result += 60 * atoi (tmp);
+
+ /* The next colon should again be exactly 2 chars right */
+ head = tail + 1;
+ tail = head + 2;
+
+ /* Bail out if tail goes too far or head is bigger than tail */
+ if (tail > len || head > tail) {
+ g_free (tmp);
+ return -1;
+ }
+
+ /* Extract seconds */
+ memcpy (tmp, time + head, tail - head);
+ tmp[2] = '\0';
+ result += atoi(tmp);
+ g_free (tmp);
+
+ return result;
+
+}
+
+static gboolean
+is_image (xmlNode *node)
+{
+ gchar *mime_type;
+ gboolean ret;
+
+ mime_type = didl_res_get_protocol_info (node, 2);
+ ret = g_str_has_prefix (mime_type, "image/");
+
+ g_free (mime_type);
+ return ret;
+}
+
+static gboolean
+is_http_get (xmlNode *node)
+{
+ gboolean ret;
+ gchar *protocol;
+
+ protocol = didl_res_get_protocol_info (node, 0);
+ ret = g_str_has_prefix (protocol, "http-get");
+
+ g_free (protocol);
+ return ret;
+}
+
+static gboolean
+has_thumbnail_marker (xmlNode *node)
+{
+ gchar *dlna_stuff;
+ gboolean ret;
+
+ dlna_stuff = didl_res_get_protocol_info (node, 3);
+ ret = strstr("JPEG_TN", dlna_stuff) != NULL;
+
+ g_free (dlna_stuff);
+ return ret;
+}
+
+static gchar *
+get_thumbnail (GList *nodes)
+{
+ GList *element;
+ gchar *val = NULL;
+ guint counter = 0;
+
+ /* chose, depending on availability, the first with DLNA.ORG_PN=JPEG_TN, or
+ * the last http-get with mimetype image/something if there is more than one
+ * http-get.
+ * This covers at least mediatomb and rygel.
+ * This could be improved by handling resolution and/or size */
+
+ for (element=nodes; element; element=g_list_next (element)) {
+ xmlNode *node = (xmlNode *)element->data;
+
+ if (is_http_get (node)) {
+ counter++;
+ if (is_image (node)) {
+ if (val)
+ g_free (val);
+ val = (gchar *) xmlNodeGetContent (node);
+
+ if (has_thumbnail_marker (node)) /* that's definitely it! */
+ return val;
+ }
+ }
+ }
+
+ if (val && counter == 1) {
+ /* There was only one element with http-get protocol: that's the uri of the
+ * media itself, not a thumbnail */
+ g_free (val);
+ val = NULL;
+ }
+
+ return val;
+}
+
+static gchar *
+get_value_for_key (GrlKeyID key_id,
+ GUPnPDIDLLiteObject *didl,
+ GList *props)
+{
+ GList* list;
+ gchar* val = NULL;
+ const gchar* upnp_key;
+
+ xmlNode *didl_node = gupnp_didl_lite_object_get_xml_node (didl);
+ upnp_key = get_upnp_key (key_id);
+
+ if (key_id == GRL_METADATA_KEY_CHILDCOUNT) {
+ val = (gchar *) xmlGetProp (didl_node,
+ (const xmlChar *) "childCount");
+ } else if (key_id == GRL_METADATA_KEY_MIME && props) {
+ val = didl_res_get_protocol_info ((xmlNode *) props->data, 2);
+ } else if (key_id == GRL_METADATA_KEY_DURATION && props) {
+ val = (gchar *) xmlGetProp ((xmlNodePtr) props->data,
+ (const xmlChar *) "duration");
+ } else if (key_id == GRL_METADATA_KEY_URL && props) {
+ val = (gchar *) xmlNodeGetContent ((xmlNode *) props->data);
+ } else if (key_id == GRL_METADATA_KEY_THUMBNAIL && props) {
+ val = g_strdup (gupnp_didl_lite_object_get_album_art (didl));
+ if (!val)
+ val = get_thumbnail (props);
+ } else if (upnp_key) {
+ list = gupnp_didl_lite_object_get_properties (didl, upnp_key);
+ if (list) {
+ val = (gchar *) xmlNodeGetContent ((xmlNode*) list->data);
+ g_list_free (list);
+ } else if (props && props->data) {
+ val = (gchar *) xmlGetProp ((xmlNodePtr) props->data,
+ (const xmlChar *) upnp_key);
+ }
+ }
+
+ return val;
+}
+
+static void
+set_metadata_value (GrlData *data,
+ GrlKeyID key_id,
+ const gchar *value)
+{
+ if (key_id == GRL_METADATA_KEY_DURATION) {
+ gint duration = didl_h_mm_ss_to_int (value);
+ if (duration >= 0) {
+ grl_data_set_int (data, GRL_METADATA_KEY_DURATION, duration);
+ }
+ } else if (key_id == GRL_METADATA_KEY_CHILDCOUNT && value) {
+ grl_data_set_int (data, GRL_METADATA_KEY_CHILDCOUNT, atoi (value));
+ } else {
+ grl_data_set_string (data, key_id, value);
+ }
+}
+
+static GrlMedia *
+build_media_from_didl (GrlMedia *content,
+ GUPnPDIDLLiteObject *didl_node,
+ GList *keys)
+{
+ const gchar *id;
+ const gchar *class;
+
+ GrlMedia *media = NULL;
+ GList *didl_props;
+ GList *iter;
+
+ GRL_DEBUG ("build_media_from_didl");
+
+ if (content) {
+ media = content;
+ } else {
+
+ if (GUPNP_IS_DIDL_LITE_CONTAINER (didl_node)) {
+ media = grl_media_box_new ();
+ } else {
+ if (!media) {
+ class = gupnp_didl_lite_object_get_upnp_class (didl_node);
+ if (class) {
+ if (g_str_has_prefix (class, "object.item.audioItem")) {
+ media = grl_media_audio_new ();
+ } else if (g_str_has_prefix (class, "object.item.videoItem")) {
+ media = grl_media_video_new ();
+ } else if (g_str_has_prefix (class, "object.item.imageItem")) {
+ media = grl_media_image_new ();
+ } else {
+ media = grl_media_new ();
+ }
+ } else {
+ media = grl_media_new ();
+ }
+ }
+ }
+ }
+
+ id = gupnp_didl_lite_object_get_id (didl_node);
+ /* Root category's id is always NULL */
+ if (g_strcmp0 (id, "0") == 0) {
+ grl_media_set_id (media, NULL);
+ } else {
+ grl_media_set_id (media, id);
+ }
+
+ didl_props = didl_get_supported_resources (didl_node);
+
+ iter = keys;
+ while (iter) {
+ gchar *value = get_value_for_key (iter->data, didl_node, didl_props);
+ if (value) {
+ set_metadata_value (GRL_DATA (media), iter->data, value);
+ }
+ iter = g_list_next (iter);
+ }
+
+ g_list_free (didl_props);
+
+ return media;
+}
+
+static void
+gupnp_browse_result_cb (GUPnPDIDLLiteParser *parser,
+ GUPnPDIDLLiteObject *didl,
+ gpointer user_data)
+{
+ GrlMedia *media;
+ struct OperationSpec *os = (struct OperationSpec *) user_data;
+ if (gupnp_didl_lite_object_get_id (didl)) {
+ media = build_media_from_didl (NULL, didl, os->keys);
+ os->callback (os->source,
+ os->operation_id,
+ media,
+ --os->count,
+ os->user_data,
+ NULL);
+ }
+}
+
+static void
+gupnp_browse_cb (GUPnPServiceProxy *service,
+ GUPnPServiceProxyAction *action,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ gchar *didl = NULL;
+ guint returned = 0;
+ guint matches = 0;
+ gboolean result;
+ struct OperationSpec *os;
+ GUPnPDIDLLiteParser *didl_parser;
+
+ GRL_DEBUG ("gupnp_browse_cb");
+
+ os = (struct OperationSpec *) user_data;
+ didl_parser = gupnp_didl_lite_parser_new ();
+
+ result =
+ gupnp_service_proxy_end_action (service, action, &error,
+ "Result", G_TYPE_STRING, &didl,
+ "NumberReturned", G_TYPE_UINT, &returned,
+ "TotalMatches", G_TYPE_UINT, &matches,
+ NULL);
+
+ if (!result) {
+ GRL_WARNING ("Operation (browse, search or query) failed");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ if (error) {
+ GRL_WARNING (" Reason: %s", error->message);
+ g_error_free (error);
+ }
+
+ goto free_resources;
+ }
+
+ if (!didl || !returned) {
+ GRL_DEBUG ("Got no results");
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
+
+ goto free_resources;
+ }
+
+ /* Use os->count to emit "remaining" information */
+ if (os->count > returned) {
+ os->count = returned;
+ }
+
+ g_signal_connect (G_OBJECT (didl_parser),
+ "object-available",
+ G_CALLBACK (gupnp_browse_result_cb),
+ os);
+ gupnp_didl_lite_parser_parse_didl (didl_parser,
+ didl,
+ &error);
+ if (error) {
+ GRL_WARNING ("Failed to parse DIDL result: %s", error->message);
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+
+ goto free_resources;
+ }
+
+ free_resources:
+ g_slice_free (struct OperationSpec, os);
+ g_free (didl);
+ g_object_unref (didl_parser);
+}
+
+static void
+gupnp_metadata_result_cb (GUPnPDIDLLiteParser *parser,
+ GUPnPDIDLLiteObject *didl,
+ gpointer user_data)
+{
+ GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
+ if (gupnp_didl_lite_object_get_id (didl)) {
+ build_media_from_didl (ms->media, didl, ms->keys);
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ }
+}
+
+static void
+gupnp_metadata_cb (GUPnPServiceProxy *service,
+ GUPnPServiceProxyAction *action,
+ gpointer user_data)
+{
+ GError *error = NULL;
+ gchar *didl = NULL;
+ gboolean result;
+ GrlMediaSourceMetadataSpec *ms;
+ GUPnPDIDLLiteParser *didl_parser;
+
+ GRL_DEBUG ("gupnp_metadata_cb");
+
+ ms = (GrlMediaSourceMetadataSpec *) user_data;
+ didl_parser = gupnp_didl_lite_parser_new ();
+
+ result =
+ gupnp_service_proxy_end_action (service, action, &error,
+ "Result", G_TYPE_STRING, &didl,
+ NULL);
+
+ if (!result) {
+ GRL_WARNING ("Metadata operation failed");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ if (error) {
+ GRL_WARNING (" Reason: %s", error->message);
+ g_error_free (error);
+ }
+
+ goto free_resources;
+ }
+
+ if (!didl) {
+ GRL_DEBUG ("Got no metadata");
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+
+ goto free_resources;
+ }
+
+ g_signal_connect (G_OBJECT (didl_parser),
+ "object-available",
+ G_CALLBACK (gupnp_metadata_result_cb),
+ ms);
+ gupnp_didl_lite_parser_parse_didl (didl_parser,
+ didl,
+ &error);
+ if (error) {
+ GRL_WARNING ("Failed to parse DIDL result: %s", error->message);
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ goto free_resources;
+ }
+
+ free_resources:
+ g_free (didl);
+ g_object_unref (didl_parser);
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_upnp_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_MIME,
+ GRL_METADATA_KEY_DATE,
+ GRL_METADATA_KEY_DURATION,
+ GRL_METADATA_KEY_ARTIST,
+ GRL_METADATA_KEY_ALBUM,
+ GRL_METADATA_KEY_GENRE,
+ GRL_METADATA_KEY_CHILDCOUNT,
+ GRL_METADATA_KEY_THUMBNAIL,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_upnp_source_browse (GrlMediaSource *source, GrlMediaSourceBrowseSpec *bs)
+{
+ GUPnPServiceProxyAction* action;
+ gchar *upnp_filter;
+ gchar *container_id;
+ GError *error = NULL;
+ struct OperationSpec *os;
+
+ GRL_DEBUG ("grl_upnp_source_browse");
+
+ upnp_filter = get_upnp_filter (bs->keys);
+ GRL_DEBUG ("filter: '%s'", upnp_filter);
+
+ os = g_slice_new0 (struct OperationSpec);
+ os->source = bs->source;
+ os->operation_id = bs->browse_id;
+ os->keys = bs->keys;
+ os->skip = bs->skip;
+ os->count = bs->count;
+ os->callback = bs->callback;
+ os->user_data = bs->user_data;
+
+ container_id = (gchar *) grl_media_get_id (bs->container);
+ if (!container_id) {
+ container_id = "0";
+ }
+
+ action =
+ gupnp_service_proxy_begin_action (GRL_UPNP_SOURCE (source)->priv->service,
+ "Browse", gupnp_browse_cb,
+ os,
+ "ObjectID", G_TYPE_STRING,
+ container_id,
+ "BrowseFlag", G_TYPE_STRING,
+ "BrowseDirectChildren",
+ "Filter", G_TYPE_STRING,
+ upnp_filter,
+ "StartingIndex", G_TYPE_UINT,
+ bs->skip,
+ "RequestedCount", G_TYPE_UINT,
+ bs->count,
+ "SortCriteria", G_TYPE_STRING,
+ "",
+ NULL);
+ if (!action) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Failed to start browse action");
+ bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, error);
+ g_error_free (error);
+ g_slice_free (struct OperationSpec, os);
+ }
+
+ g_free (upnp_filter);
+}
+
+static void
+grl_upnp_source_search (GrlMediaSource *source, GrlMediaSourceSearchSpec *ss)
+{
+ GUPnPServiceProxyAction* action;
+ gchar *upnp_filter;
+ GError *error = NULL;
+ gchar *upnp_search;
+ struct OperationSpec *os;
+
+ GRL_DEBUG ("grl_upnp_source_search");
+
+ upnp_filter = get_upnp_filter (ss->keys);
+ GRL_DEBUG ("filter: '%s'", upnp_filter);
+
+ upnp_search = get_upnp_search (ss->text);
+ GRL_DEBUG ("search: '%s'", upnp_search);
+
+ os = g_slice_new0 (struct OperationSpec);
+ os->source = ss->source;
+ os->operation_id = ss->search_id;
+ os->keys = ss->keys;
+ os->skip = ss->skip;
+ os->count = ss->count;
+ os->callback = ss->callback;
+ os->user_data = ss->user_data;
+
+ action =
+ gupnp_service_proxy_begin_action (GRL_UPNP_SOURCE (source)->priv->service,
+ "Search", gupnp_browse_cb,
+ os,
+ "ContainerID", G_TYPE_STRING,
+ "0",
+ "SearchCriteria", G_TYPE_STRING,
+ upnp_search,
+ "Filter", G_TYPE_STRING,
+ upnp_filter,
+ "StartingIndex", G_TYPE_UINT,
+ ss->skip,
+ "RequestedCount", G_TYPE_UINT,
+ ss->count,
+ "SortCriteria", G_TYPE_STRING,
+ "",
+ NULL);
+ if (!action) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_SEARCH_FAILED,
+ "Failed to start browse action");
+ ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, error);
+ g_error_free (error);
+ g_slice_free (struct OperationSpec, os);
+ }
+
+ g_free (upnp_filter);
+ g_free (upnp_search);
+}
+
+/*
+ * Query format is the UPnP ContentDirectory SearchCriteria format, e.g.
+ * 'upnp:artist contains "Rick Astley" and
+ * (upnp:class derivedfrom "object.item.audioItem")'
+ *
+ * Note that we don't guarantee or check that the server actually
+ * supports the given criteria. Offering the searchcaps as
+ * additional metadata to clients that _really_ are interested might
+ * be useful.
+ */
+static void
+grl_upnp_source_query (GrlMediaSource *source, GrlMediaSourceQuerySpec *qs)
+{
+ GUPnPServiceProxyAction* action;
+ gchar *upnp_filter;
+ GError *error = NULL;
+ struct OperationSpec *os;
+
+ GRL_DEBUG (__func__);
+
+ upnp_filter = get_upnp_filter (qs->keys);
+ GRL_DEBUG ("filter: '%s'", upnp_filter);
+
+ GRL_DEBUG ("query: '%s'", qs->query);
+
+ os = g_slice_new0 (struct OperationSpec);
+ os->source = qs->source;
+ os->operation_id = qs->query_id;
+ os->keys = qs->keys;
+ os->skip = qs->skip;
+ os->count = qs->count;
+ os->callback = qs->callback;
+ os->user_data = qs->user_data;
+
+ action =
+ gupnp_service_proxy_begin_action (GRL_UPNP_SOURCE (source)->priv->service,
+ "Search", gupnp_browse_cb, os,
+ "ContainerID", G_TYPE_STRING,
+ "0",
+ "SearchCriteria", G_TYPE_STRING,
+ qs->query,
+ "Filter", G_TYPE_STRING,
+ upnp_filter,
+ "StartingIndex", G_TYPE_UINT,
+ qs->skip,
+ "RequestedCount", G_TYPE_UINT,
+ qs->count,
+ "SortCriteria", G_TYPE_STRING,
+ "",
+ NULL);
+ if (!action) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_QUERY_FAILED,
+ "Failed to start query action");
+ qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
+ g_error_free (error);
+ g_slice_free (struct OperationSpec, os);
+ }
+
+ g_free (upnp_filter);
+}
+
+static void
+grl_upnp_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ GUPnPServiceProxyAction* action;
+ gchar *upnp_filter;
+ gchar *id;
+ GError *error = NULL;
+
+ GRL_DEBUG ("grl_upnp_source_metadata");
+
+ upnp_filter = get_upnp_filter (ms->keys);
+
+ GRL_DEBUG ("filter: '%s'", upnp_filter);
+
+ id = (gchar *) grl_media_get_id (ms->media);
+ if (!id) {
+ grl_media_set_title (ms->media, GRL_UPNP_SOURCE (source)->priv->upnp_name);
+ id = "0";
+ }
+
+ action =
+ gupnp_service_proxy_begin_action (GRL_UPNP_SOURCE (source)->priv->service,
+ "Browse", gupnp_metadata_cb,
+ ms,
+ "ObjectID", G_TYPE_STRING,
+ id,
+ "BrowseFlag", G_TYPE_STRING,
+ "BrowseMetadata",
+ "Filter", G_TYPE_STRING,
+ upnp_filter,
+ "StartingIndex", G_TYPE_UINT,
+ 0,
+ "RequestedCount", G_TYPE_UINT,
+ 0,
+ "SortCriteria", G_TYPE_STRING,
+ "",
+ NULL);
+ if (!action) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Failed to start metadata action");
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ }
+
+ g_free (upnp_filter);
+}
+
+static GrlSupportedOps
+grl_upnp_source_supported_operations (GrlMetadataSource *metadata_source)
+{
+ GrlSupportedOps caps;
+ GrlUpnpSource *source;
+
+ /* Some sources may support search() while other not, so we rewrite
+ supported_operations() to take that into account.
+ See also note in grl_upnp_source_query() */
+
+ source = GRL_UPNP_SOURCE (metadata_source);
+ caps = GRL_OP_BROWSE | GRL_OP_METADATA | GRL_OP_NOTIFY_CHANGE;
+ if (source->priv->search_enabled)
+ caps = caps | GRL_OP_SEARCH | GRL_OP_QUERY;
+
+ return caps;
+}
+
+static gboolean
+grl_upnp_source_notify_change_start (GrlMediaSource *source,
+ GError **error)
+{
+ GrlUpnpSource *upnp_source = GRL_UPNP_SOURCE (source);
+
+ if (!gupnp_service_proxy_add_notify (upnp_source->priv->service,
+ "ContainerUpdateIDs",
+ G_TYPE_STRING,
+ container_changed_cb,
+ source)) {
+ g_set_error (error,
+ GRL_CORE_ERROR,
+ GRL_CORE_ERROR_NOTIFY_CHANGED_FAILED,
+ "Unable to listen for changes in %s",
+ grl_metadata_source_get_id (GRL_METADATA_SOURCE (source)));
+ return FALSE;
+ }
+ gupnp_service_proxy_set_subscribed (upnp_source->priv->service, TRUE);
+
+ return TRUE;
+}
+
+
+static gboolean
+grl_upnp_source_notify_change_stop (GrlMediaSource *source,
+ GError **error)
+{
+ GrlUpnpSource *upnp_source = GRL_UPNP_SOURCE (source);
+
+ gupnp_service_proxy_set_subscribed (upnp_source->priv->service, FALSE);
+ gupnp_service_proxy_remove_notify (upnp_source->priv->service,
+ "ContainerUpdateIDs",
+ container_changed_cb,
+ source);
+
+ return TRUE;
+}
diff --git a/src/media/upnp/grl-upnp.h b/src/media/upnp/grl-upnp.h
new file mode 100644
index 0000000..e0bc748
--- /dev/null
+++ b/src/media/upnp/grl-upnp.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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_UPNP_SOURCE_H_
+#define _GRL_UPNP_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_UPNP_SOURCE_TYPE \
+ (grl_upnp_source_get_type ())
+
+#define GRL_UPNP_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_UPNP_SOURCE_TYPE, \
+ GrlUpnpSource))
+
+#define GRL_IS_UPNP_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_UPNP_SOURCE_TYPE))
+
+#define GRL_UPNP_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_UPNP_SOURCE_TYPE, \
+ GrlUpnpSourceClass))
+
+#define GRL_IS_UPNP_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_UPNP_SOURCE_TYPE))
+
+#define GRL_UPNP_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_UPNP_SOURCE_TYPE, \
+ GrlUpnpSourceClass))
+
+typedef struct _GrlUpnpPrivate GrlUpnpPrivate;
+typedef struct _GrlUpnpSource GrlUpnpSource;
+
+struct _GrlUpnpSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlUpnpPrivate *priv;
+};
+
+typedef struct _GrlUpnpSourceClass GrlUpnpSourceClass;
+
+struct _GrlUpnpSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_upnp_source_get_type (void);
+
+#endif /* _GRL_UPNP_SOURCE_H_ */
diff --git a/src/media/upnp/grl-upnp.xml b/src/media/upnp/grl-upnp.xml
new file mode 100644
index 0000000..3617b98
--- /dev/null
+++ b/src/media/upnp/grl-upnp.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>UPnP</name>
+ <description>A plugin for browsing UPnP servers</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/vimeo/Makefile.am b/src/media/vimeo/Makefile.am
new file mode 100644
index 0000000..b810218
--- /dev/null
+++ b/src/media/vimeo/Makefile.am
@@ -0,0 +1,42 @@
+#
+# Makefile.am
+#
+# Author: Joaquim Rocha <jrocha igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlvimeo.la
+
+libgrlvimeo_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(XML_CFLAGS) \
+ $(GTHREAD_CFLAGS) \
+ $(LIBSOUP_CFLAGS)
+
+libgrlvimeo_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(XML_LIBS) \
+ $(GTHREAD_LIBS) \
+ $(LIBSOUP_LIBS)
+
+libgrlvimeo_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlvimeo_la_SOURCES = \
+ grl-vimeo.c \
+ grl-vimeo.h \
+ gvimeo.c \
+ gvimeo.h
+
+libdir=$(GRL_PLUGINS_DIR)
+vimeoxmldir = $(GRL_PLUGINS_CONF_DIR)
+vimeoxml_DATA = $(VIMEO_PLUGIN_ID).xml
+
+EXTRA_DIST = $(vimeoxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/vimeo/grl-vimeo.c b/src/media/vimeo/grl-vimeo.c
new file mode 100644
index 0000000..a235217
--- /dev/null
+++ b/src/media/vimeo/grl-vimeo.c
@@ -0,0 +1,413 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Joaquim Rocha <jrocha igalia 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 <grilo.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "grl-vimeo.h"
+#include "gvimeo.h"
+
+#define GRL_VIMEO_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_VIMEO_SOURCE_TYPE, \
+ GrlVimeoSourcePrivate))
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT vimeo_log_domain
+GRL_LOG_DOMAIN_STATIC(vimeo_log_domain);
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID VIMEO_PLUGIN_ID
+
+#define SOURCE_ID "grl-vimeo"
+#define SOURCE_NAME "Vimeo"
+#define SOURCE_DESC "A source for browsing and searching Vimeo videos"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+typedef struct {
+ GrlMediaSourceSearchSpec *ss;
+ gint offset;
+ gint page;
+} SearchData;
+
+struct _GrlVimeoSourcePrivate {
+ GVimeo *vimeo;
+};
+
+static GrlVimeoSource *grl_vimeo_source_new (void);
+
+gboolean grl_vimeo_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+static const GList *grl_vimeo_source_supported_keys (GrlMetadataSource *source);
+
+static void grl_vimeo_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ss);
+
+static void grl_vimeo_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+
+/* =================== Vimeo Plugin =============== */
+
+gboolean
+grl_vimeo_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ gchar *vimeo_key;
+ gchar *vimeo_secret;
+ GrlConfig *config;
+ gint config_count;
+ gboolean init_result = FALSE;
+ GrlVimeoSource *source;
+
+ GRL_LOG_DOMAIN_INIT (vimeo_log_domain, "vimeo");
+
+ GRL_DEBUG ("vimeo_plugin_init");
+
+ if (!g_thread_supported ()) {
+ g_thread_init (NULL);
+ }
+
+ if (!configs) {
+ GRL_WARNING ("Missing configuration");
+ return FALSE;
+ }
+
+ config_count = g_list_length (configs);
+ if (config_count > 1) {
+ GRL_WARNING ("Provided %d configs, but will only use one", config_count);
+ }
+
+ config = GRL_CONFIG (configs->data);
+
+ vimeo_key = grl_config_get_api_key (config);
+ vimeo_secret = grl_config_get_api_secret (config);
+
+ if (!vimeo_key || !vimeo_secret) {
+ GRL_WARNING ("Required configuration keys not set up");
+ goto go_out;
+ }
+
+ source = grl_vimeo_source_new ();
+ source->priv->vimeo = g_vimeo_new (vimeo_key, vimeo_secret);
+
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ init_result = TRUE;
+
+ go_out:
+
+ if (vimeo_key != NULL)
+ g_free (vimeo_key);
+ if (vimeo_secret != NULL)
+ g_free (vimeo_secret);
+
+ return init_result;
+}
+
+GRL_PLUGIN_REGISTER (grl_vimeo_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Vimeo GObject ================ */
+
+static GrlVimeoSource *
+grl_vimeo_source_new (void)
+{
+ GRL_DEBUG ("grl_vimeo_source_new");
+
+ return g_object_new (GRL_VIMEO_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_vimeo_source_class_init (GrlVimeoSourceClass * klass)
+{
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+
+ source_class->metadata = grl_vimeo_source_metadata;
+ source_class->search = grl_vimeo_source_search;
+ metadata_class->supported_keys = grl_vimeo_source_supported_keys;
+
+ g_type_class_add_private (klass, sizeof (GrlVimeoSourcePrivate));
+}
+
+static void
+grl_vimeo_source_init (GrlVimeoSource *source)
+{
+ source->priv = GRL_VIMEO_SOURCE_GET_PRIVATE (source);
+}
+
+G_DEFINE_TYPE (GrlVimeoSource, grl_vimeo_source, GRL_TYPE_MEDIA_SOURCE);
+
+/* ======================= Utilities ==================== */
+
+static gint
+str_to_gint (gchar *str)
+{
+ gint number;
+
+ errno = 0;
+ number = (gint) g_ascii_strtod (str, NULL);
+ if (errno == 0)
+ {
+ return number;
+ }
+ return 0;
+}
+
+static void
+update_media (GrlMedia *media, GHashTable *video)
+{
+ gchar *str;
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_ID);
+ if (str)
+ {
+ grl_media_set_id (media, str);
+ }
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_TITLE);
+ if (str)
+ {
+ grl_media_set_title (media, str);
+ }
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_DESCRIPTION);
+ if (str)
+ {
+ grl_media_set_description (media, str);
+ }
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_DURATION);
+ if (str)
+ {
+ grl_media_set_duration (media, str_to_gint (str));
+ }
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_OWNER_NAME);
+ if (str)
+ {
+ grl_media_set_author (media, str);
+ }
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_UPLOAD_DATE);
+ if (str)
+ {
+ grl_media_set_date (media, str);
+ }
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_THUMBNAIL);
+ if (str)
+ {
+ grl_media_set_thumbnail (media, str);
+ }
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_WIDTH);
+ if (str)
+ {
+ grl_media_video_set_width (GRL_MEDIA_VIDEO (media), str_to_gint (str));
+ }
+
+ str = g_hash_table_lookup (video, VIMEO_VIDEO_HEIGHT);
+ if (str)
+ {
+ grl_media_video_set_height (GRL_MEDIA_VIDEO (media), str_to_gint (str));
+ }
+}
+
+
+static void
+search_cb (GVimeo *vimeo, GList *video_list, gpointer user_data)
+{
+ GrlMedia *media = NULL;
+ SearchData *sd = (SearchData *) user_data;
+ gint count = sd->ss->count;
+ gchar *media_type;
+
+ /* Go to offset element */
+ video_list = g_list_nth (video_list, sd->offset);
+
+ /* No more elements can be sent */
+ if (!video_list) {
+ sd->ss->callback (sd->ss->source,
+ sd->ss->search_id,
+ NULL,
+ 0,
+ sd->ss->user_data,
+ NULL);
+ g_slice_free (SearchData, sd);
+ return;
+ }
+
+ while (video_list && count)
+ {
+ media_type = g_hash_table_lookup (video_list->data, "title");
+ if (media_type) {
+ media = grl_media_video_new ();
+ }
+
+ if (media)
+ {
+ update_media (media, video_list->data);
+ sd->ss->callback (sd->ss->source,
+ sd->ss->search_id,
+ media,
+ sd->ss->count == 1? 0: -1,
+ sd->ss->user_data,
+ NULL);
+ }
+ video_list = g_list_next (video_list);
+
+ if (--count)
+ sd->ss->count = count;
+
+ media = NULL;
+ }
+
+ /* Get more elements */
+ if (count)
+ {
+ sd->offset = 0;
+ sd->page++;
+ g_vimeo_videos_search (vimeo, sd->ss->text, sd->page, search_cb, sd);
+ }
+ else
+ {
+ g_slice_free (SearchData, sd);
+ }
+}
+
+static void
+video_get_play_url_cb (gchar *url, gpointer user_data)
+{
+ GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
+
+ if (url)
+ {
+ grl_media_set_url (ms->media, url);
+ }
+
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_vimeo_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_DESCRIPTION,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_AUTHOR,
+ GRL_METADATA_KEY_DATE,
+ GRL_METADATA_KEY_THUMBNAIL,
+ GRL_METADATA_KEY_DURATION,
+ GRL_METADATA_KEY_WIDTH,
+ GRL_METADATA_KEY_HEIGHT,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_vimeo_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ gint id;
+ const gchar *id_str;
+
+ if (!ms->media || (id_str = grl_media_get_id (ms->media)) == NULL)
+ {
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ return;
+ }
+
+ errno = 0;
+ id = (gint) g_ascii_strtod (id_str, NULL);
+ if (errno != 0)
+ {
+ return;
+ }
+
+ g_vimeo_video_get_play_url (GRL_VIMEO_SOURCE (source)->priv->vimeo,
+ id,
+ video_get_play_url_cb,
+ ms);
+}
+
+static void
+grl_vimeo_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss)
+{
+ SearchData *sd;
+ GError *error;
+ gint per_page;
+ GVimeo *vimeo = GRL_VIMEO_SOURCE (source)->priv->vimeo;
+
+ if (!ss->text) {
+ /* Vimeo does not support searching all */
+ error =
+ g_error_new_literal (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_SEARCH_NULL_UNSUPPORTED,
+ "Unable to execute search: non NULL search text is required");
+ ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* Compute items per page and page offset */
+ per_page = CLAMP (1 + ss->skip + ss->count, 0, 100);
+ g_vimeo_set_per_page (vimeo, per_page);
+
+ sd = g_slice_new (SearchData);
+ sd->page = 1 + (ss->skip / per_page);
+ sd->offset = ss->skip % per_page;
+ sd->ss = ss;
+
+ g_vimeo_videos_search (vimeo, ss->text, sd->page, search_cb, sd);
+}
diff --git a/src/media/vimeo/grl-vimeo.h b/src/media/vimeo/grl-vimeo.h
new file mode 100644
index 0000000..fde4f36
--- /dev/null
+++ b/src/media/vimeo/grl-vimeo.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Joaquim Rocha <jrocha igalia 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_VIMEO_SOURCE_H_
+#define _GRL_VIMEO_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_VIMEO_SOURCE_TYPE \
+ (grl_vimeo_source_get_type ())
+
+#define GRL_VIMEO_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_VIMEO_SOURCE_TYPE, \
+ GrlVimeoSource))
+
+#define GRL_IS_VIMEO_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_VIMEO_SOURCE_TYPE))
+
+#define GRL_VIMEO_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_VIMEO_SOURCE_TYPE, \
+ GrlVimeoSourceClass))
+
+#define GRL_IS_VIMEO_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_VIMEO_SOURCE_TYPE))
+
+#define GRL_VIMEO_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_VIMEO_SOURCE_TYPE, \
+ GrlVimeoSourceClass))
+
+typedef struct _GrlVimeoSource GrlVimeoSource;
+typedef struct _GrlVimeoSourcePrivate GrlVimeoSourcePrivate;
+
+struct _GrlVimeoSource {
+
+ GrlMediaSource parent;
+
+ /*< private >*/
+ GrlVimeoSourcePrivate *priv;
+};
+
+typedef struct _GrlVimeoSourceClass GrlVimeoSourceClass;
+
+struct _GrlVimeoSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_vimeo_source_get_type (void);
+
+#endif /* _GRL_VIMEO_SOURCE_H_ */
diff --git a/src/media/vimeo/grl-vimeo.xml b/src/media/vimeo/grl-vimeo.xml
new file mode 100644
index 0000000..b80c9d9
--- /dev/null
+++ b/src/media/vimeo/grl-vimeo.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Vimeo</name>
+ <description>A plugin for searching Vimeo videos</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/media/vimeo/gvimeo.c b/src/media/vimeo/gvimeo.c
new file mode 100644
index 0000000..3b7f2fc
--- /dev/null
+++ b/src/media/vimeo/gvimeo.c
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Joaquim Rocha <jrocha igalia 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
+ *
+ */
+
+#include <gcrypt.h>
+#include <libsoup/soup.h>
+#include "gvimeo.h"
+#include <libxml/parser.h>
+#include <libxml/xpath.h>
+
+#define G_VIMEO_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ G_VIMEO_TYPE, \
+ GVimeoPrivate))
+
+#define PLUGIN_USER_AGENT "Grilo Vimeo Plugin"
+
+#define VIMEO_ENDPOINT "http://vimeo.com/api/rest/v2"
+#define VIMEO_VIDEO_LOAD_URL "http://vimeo.com/moogaloop/load/clip:"
+#define VIMEO_VIDEO_PLAY_URL "http://vimeo.com/moogaloop/play/clip:"
+
+#define VIMEO_VIDEO_SEARCH_METHOD "vimeo.videos.search"
+#define VIMEO_API_OAUTH_SIGN_METHOD "HMAC-SHA1"
+#define VIMEO_API_OAUTH_SIGNATURE_PARAM "&oauth_signature=%s"
+
+#define VIMEO_VIDEO_SEARCH \
+ "full_response=yes" \
+ "&method=%s" \
+ "&oauth_consumer_key=%s" \
+ "&oauth_nonce=%s" \
+ "&oauth_signature_method=" VIMEO_API_OAUTH_SIGN_METHOD \
+ "&oauth_timestamp=%s" \
+ "&oauth_token=" \
+ "&page=%d" \
+ "&per_page=%d" \
+ "&query=%s"
+
+typedef struct {
+ GVimeo *vimeo;
+ GVimeoVideoSearchCb search_cb;
+ gpointer user_data;
+} GVimeoVideoSearchData;
+
+typedef struct {
+ GVimeo *vimeo;
+ gint video_id;
+ GVimeoURLCb callback;
+ gpointer user_data;
+} GVimeoVideoURLData;
+
+struct _GVimeoPrivate {
+ gchar *api_key;
+ gchar *auth_token;
+ gchar *auth_secret;
+ gint per_page;
+ SoupSession *async_session;
+};
+
+enum InfoType {SIMPLE, EXTENDED};
+
+typedef struct {
+ enum InfoType type;
+ gchar *name;
+} VideoInfo;
+
+static VideoInfo video_info[] = {{SIMPLE, VIMEO_VIDEO_TITLE},
+ {SIMPLE, VIMEO_VIDEO_DESCRIPTION},
+ {SIMPLE, VIMEO_VIDEO_UPLOAD_DATE},
+ {SIMPLE, VIMEO_VIDEO_WIDTH},
+ {SIMPLE, VIMEO_VIDEO_HEIGHT},
+ {SIMPLE, VIMEO_VIDEO_OWNER},
+ {SIMPLE, VIMEO_VIDEO_URL},
+ {SIMPLE, VIMEO_VIDEO_THUMBNAIL},
+ {SIMPLE, VIMEO_VIDEO_DURATION},
+ {EXTENDED, VIMEO_VIDEO_OWNER}};
+
+static void g_vimeo_finalize (GObject *object);
+static gchar * encode_uri (const gchar *uri);
+
+/* -------------------- GOBJECT -------------------- */
+
+G_DEFINE_TYPE (GVimeo, g_vimeo, G_TYPE_OBJECT);
+
+static void
+g_vimeo_class_init (GVimeoClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = g_vimeo_finalize;
+
+ g_type_class_add_private (klass, sizeof (GVimeoPrivate));
+}
+
+static void
+g_vimeo_init (GVimeo *vimeo)
+{
+ vimeo->priv = G_VIMEO_GET_PRIVATE (vimeo);
+ vimeo->priv->per_page = 50;
+ vimeo->priv->async_session = soup_session_async_new ();
+}
+
+static void
+g_vimeo_finalize (GObject *object)
+{
+ GVimeo *vimeo = G_VIMEO (object);
+ g_free (vimeo->priv->api_key);
+ g_free (vimeo->priv->auth_secret);
+ g_object_unref (vimeo->priv->async_session);
+
+ G_OBJECT_CLASS (g_vimeo_parent_class)->finalize (object);
+}
+
+GVimeo *
+g_vimeo_new (const gchar *api_key, const gchar *auth_secret)
+{
+ GVimeo *vimeo = g_object_new (G_VIMEO_TYPE, NULL);
+ vimeo->priv->api_key = g_strdup (api_key);
+ vimeo->priv->auth_secret = g_strdup (auth_secret);
+
+ return vimeo;
+}
+
+/* -------------------- PRIVATE API -------------------- */
+
+static gchar *
+get_timestamp (void)
+{
+ time_t t = time (NULL);
+ return g_strdup_printf ("%d", (gint) t);
+}
+
+static gchar *
+get_nonce (void)
+{
+ gchar *timestamp = get_timestamp();
+ guint rnd_number = g_random_int ();
+ gchar *rnd_str = g_strdup_printf ("%d_%s", rnd_number, timestamp);
+ gchar *nonce = g_compute_checksum_for_string (G_CHECKSUM_MD5, rnd_str, -1);
+ g_free (timestamp);
+ g_free (rnd_str);
+
+ return nonce;
+}
+
+static gchar *
+get_videos_search_params (GVimeo *vimeo, const gchar *text, gint page) {
+ gchar *encoded_text = encode_uri (text);
+ gchar *timestamp = get_timestamp ();
+ gchar *nonce = get_nonce ();
+ gchar *params = g_strdup_printf (VIMEO_VIDEO_SEARCH,
+ VIMEO_VIDEO_SEARCH_METHOD,
+ vimeo->priv->api_key,
+ nonce,
+ timestamp,
+ page,
+ vimeo->priv->per_page,
+ encoded_text);
+ g_free (timestamp);
+ g_free (nonce);
+ g_free (encoded_text);
+
+ return params;
+}
+
+static gchar *
+sign_string (gchar *message, gchar *key)
+{
+ gchar *signed_message = NULL;
+ gcry_md_hd_t digest_obj;
+ unsigned char *hmac_digest;
+ guint digest_len;
+
+ gcry_md_open(&digest_obj,
+ GCRY_MD_SHA1,
+ GCRY_MD_FLAG_SECURE | GCRY_MD_FLAG_HMAC);
+ gcry_md_setkey(digest_obj, key, strlen (key));
+ gcry_md_write (digest_obj, message, strlen (message));
+ gcry_md_final (digest_obj);
+ hmac_digest = gcry_md_read (digest_obj, 0);
+
+ digest_len = gcry_md_get_algo_dlen (GCRY_MD_SHA1);
+ signed_message = g_base64_encode (hmac_digest, digest_len);
+
+ gcry_md_close (digest_obj);
+
+ return signed_message;
+}
+
+static gboolean
+result_is_correct (xmlNodePtr node)
+{
+ gboolean correct = FALSE;
+ xmlChar *stat;
+
+ if (xmlStrcmp (node->name, (const xmlChar *) "rsp") == 0)
+ {
+ stat = xmlGetProp (node, (const xmlChar *) "stat");
+ if (stat && xmlStrcmp (stat, (const xmlChar *) "ok") == 0)
+ {
+ correct = TRUE;
+ xmlFree (stat);
+ }
+ }
+
+ return correct;
+}
+
+static void
+add_node (xmlNodePtr node, GHashTable *video)
+{
+ xmlAttrPtr attr;
+
+ for (attr = node->properties; attr != NULL; attr = attr->next)
+ {
+ g_hash_table_insert (video,
+ g_strconcat ((const gchar *) node->name,
+ "_",
+ (const gchar *) attr->name,
+ NULL),
+ (gchar *) xmlGetProp (node, attr->name));
+ }
+}
+
+static xmlNodePtr
+xpath_get_node (xmlXPathContextPtr context, gchar *xpath_expr)
+{
+ xmlNodePtr node = NULL;
+ xmlXPathObjectPtr xpath_obj;
+ xpath_obj = xmlXPathEvalExpression ((xmlChar *) xpath_expr, context);
+
+ if (xpath_obj && xpath_obj->nodesetval->nodeTab)
+ {
+ node = xpath_obj->nodesetval->nodeTab[0];
+ }
+ xmlXPathFreeObject (xpath_obj);
+
+ return node;
+}
+
+static gchar *
+get_node_text (xmlXPathContextPtr context, gchar *xpath_expr)
+{
+ xmlNodePtr node;
+ gchar *node_text = NULL;
+
+ node = xpath_get_node (context, xpath_expr);
+ if (node)
+ {
+ node_text = (gchar *) xmlNodeGetContent (node);
+ }
+
+ return node_text;
+}
+
+static GHashTable *
+get_video (xmlNodePtr node)
+{
+ gint i;
+ gint array_length;
+ xmlXPathContextPtr context;
+ gchar *video_id;
+ GHashTable *video = g_hash_table_new_full (g_str_hash,
+ g_str_equal,
+ g_free,
+ g_free);
+
+ /* Adds the video node's properties */
+ add_node (node, video);
+
+ context = xmlXPathNewContext (node->doc);
+ video_id = (gchar *) xmlGetProp (node, (xmlChar *) "id");
+
+ array_length = G_N_ELEMENTS (video_info);
+ for (i = 0; i < array_length; i++)
+ {
+ /* Look for the wanted information only under the current video */
+ gchar *xpath_name = g_strdup_printf ("//video[@id=%s]//%s",
+ video_id,
+ video_info[i].name);
+ xmlNodePtr info_node = xpath_get_node (context, xpath_name);
+ if (info_node)
+ {
+ if (video_info[i].type == EXTENDED) {
+ add_node (info_node, video);
+ }
+ else
+ {
+ g_hash_table_insert (video,
+ g_strdup ((const gchar *) info_node->name),
+ (gchar *) xmlNodeGetContent (info_node));
+ }
+ }
+ g_free (xpath_name);
+ }
+ g_free (video_id);
+
+ xmlXPathFreeContext (context);
+
+ return video;
+}
+
+
+static void
+process_video_search_result (const gchar *xml_result, gpointer user_data)
+{
+ xmlDocPtr doc;
+ xmlNodePtr node;
+ GList *video_list = NULL;
+ GVimeoVideoSearchData *data = (GVimeoVideoSearchData *) user_data;
+
+ doc = xmlReadMemory (xml_result,
+ xmlStrlen ((xmlChar *) xml_result),
+ NULL,
+ NULL,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ node = xmlDocGetRootElement (doc);
+
+ /* Check result is ok */
+ if (!node || !result_is_correct (node))
+ {
+ data->search_cb (data->vimeo, NULL, data->user_data);
+ }
+ else
+ {
+ node = node->xmlChildrenNode;
+
+ /* Now we're at "video pages" node */
+ node = node->xmlChildrenNode;
+ while (node)
+ {
+ video_list = g_list_prepend (video_list, get_video (node));
+ node = node->next;
+ }
+
+ data->search_cb (data->vimeo, g_list_reverse (video_list), data->user_data);
+ g_list_foreach (video_list, (GFunc) g_hash_table_unref, NULL);
+ g_list_free (video_list);
+ }
+ g_slice_free (GVimeoVideoSearchData, data);
+ xmlFreeDoc (doc);
+}
+
+static void
+search_videos_complete_cb (SoupSession *session,
+ SoupMessage *message,
+ gpointer *data)
+{
+ GVimeoVideoSearchCb *search_data = (GVimeoVideoSearchCb *) data;
+ process_video_search_result (message->response_body->data, search_data);
+}
+
+static gchar *
+get_play_url_from_vimeo_xml (const gchar *xml, gint video_id)
+{
+ xmlDocPtr doc = xmlRecoverDoc ((xmlChar *) xml);
+ xmlXPathContextPtr context = xmlXPathNewContext (doc);
+ gchar *request_signature = get_node_text (context,
+ "/xml/request_signature[1]");
+ gchar *signature_expires = get_node_text (context,
+ "/xml/request_signature_expires[1]");
+
+ gchar *url = g_strdup_printf ("%s%d/%s/%s/?q=sd",
+ VIMEO_VIDEO_PLAY_URL,
+ video_id,
+ request_signature,
+ signature_expires);
+
+ g_free (request_signature);
+ g_free (signature_expires);
+ xmlXPathFreeContext (context);
+ xmlFreeDoc (doc);
+
+ return url;
+}
+
+static void
+get_video_play_url_complete_cb (SoupSession *session,
+ SoupMessage *message,
+ gpointer *data)
+{
+ GVimeoVideoURLData *url_data;
+ gchar *url;
+
+ if (message->response_body == NULL)
+ {
+ return;
+ }
+
+ url_data = (GVimeoVideoURLData *) data;
+ url = get_play_url_from_vimeo_xml (message->response_body->data,
+ url_data->video_id);
+ url_data->callback (url, url_data->user_data);
+ g_slice_free (GVimeoVideoURLData, url_data);
+}
+
+static gchar *
+encode_uri (const gchar *uri)
+{
+ return soup_uri_encode (uri, "%!*'();:@&=+$,/?#[] ");
+}
+
+static gchar *
+build_request (GVimeo *vimeo, const gchar *query, gint page)
+{
+ gchar *params;
+ gchar *endpoint_encoded;
+ gchar *key;
+ gchar *escaped_str;
+ gchar *tmp_str;
+ gchar *signature;
+
+ g_return_val_if_fail (G_IS_VIMEO (vimeo), NULL);
+
+ params = get_videos_search_params (vimeo, query, page);
+ endpoint_encoded = encode_uri (VIMEO_ENDPOINT);
+ key = g_strdup_printf ("%s&", vimeo->priv->auth_secret);
+ escaped_str = encode_uri (params);
+ tmp_str = g_strdup_printf ("GET&%s&%s", endpoint_encoded, escaped_str);
+ signature = sign_string (tmp_str, key);
+ g_free (escaped_str);
+ g_free (tmp_str);
+ escaped_str = encode_uri (signature);
+ tmp_str = g_strdup_printf ("%s?%s" VIMEO_API_OAUTH_SIGNATURE_PARAM,
+ VIMEO_ENDPOINT,
+ params,
+ escaped_str);
+
+ g_free (endpoint_encoded);
+ g_free (params);
+ g_free (key);
+ g_free (escaped_str);
+ g_free (signature);
+
+ return tmp_str;
+}
+
+/* -------------------- PUBLIC API -------------------- */
+
+void
+g_vimeo_set_per_page (GVimeo *vimeo, gint per_page)
+{
+ g_return_if_fail (G_IS_VIMEO (vimeo));
+ vimeo->priv->per_page = per_page;
+}
+
+void
+g_vimeo_videos_search (GVimeo *vimeo,
+ const gchar *text,
+ gint page,
+ GVimeoVideoSearchCb callback,
+ gpointer user_data)
+{
+ SoupMessage *message;
+ GVimeoVideoSearchData *search_data;
+ gchar *request;
+
+ g_return_if_fail (G_IS_VIMEO (vimeo));
+
+ request = build_request (vimeo, text, page);
+ search_data = g_slice_new (GVimeoVideoSearchData);
+ search_data->vimeo = vimeo;
+ search_data->search_cb = callback;
+ search_data->user_data = user_data;
+
+ message = soup_message_new ("GET", request);
+ soup_session_queue_message (vimeo->priv->async_session,
+ message,
+ (SoupSessionCallback) search_videos_complete_cb,
+ search_data);
+ g_free (request);
+}
+
+void
+g_vimeo_video_get_play_url (GVimeo *vimeo,
+ gint id,
+ GVimeoURLCb callback,
+ gpointer user_data)
+{
+ GVimeoVideoURLData *data;
+ gchar *url = g_strdup_printf ("%s%d",
+ VIMEO_VIDEO_LOAD_URL,
+ id);
+ SoupMessage *message = soup_message_new ("GET", url);
+ SoupMessageHeaders *headers = message->request_headers;
+ soup_message_headers_append (headers, "User-Agent", PLUGIN_USER_AGENT);
+
+ data = g_slice_new (GVimeoVideoURLData);
+ data->video_id = id;
+ data->vimeo = vimeo;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ soup_session_queue_message (vimeo->priv->async_session,
+ message,
+ (SoupSessionCallback) get_video_play_url_complete_cb,
+ data);
+ g_free (url);
+}
diff --git a/src/media/vimeo/gvimeo.h b/src/media/vimeo/gvimeo.h
new file mode 100644
index 0000000..34e17a3
--- /dev/null
+++ b/src/media/vimeo/gvimeo.h
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Joaquim Rocha <jrocha igalia 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 _G_VIMEO_H_
+#define _G_VIMEO_H_
+
+#include <glib-object.h>
+
+#define G_VIMEO_TYPE \
+ (g_vimeo_get_type ())
+
+#define G_VIMEO(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ G_VIMEO_TYPE, \
+ GVimeo))
+
+#define G_IS_VIMEO(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ G_VIMEO_TYPE))
+
+#define G_VIMEO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ G_VIMEO_TYPE, \
+ GVimeoClass))
+
+#define G_IS_VIMEO_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ G_VIMEO_TYPE))
+
+#define G_VIMEO_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ G_VIMEO_TYPE, \
+ GVimeoClass))
+
+#define VIMEO_VIDEO "video"
+#define VIMEO_VIDEO_ID VIMEO_VIDEO "_id"
+#define VIMEO_VIDEO_TITLE "title"
+#define VIMEO_VIDEO_DESCRIPTION "description"
+#define VIMEO_VIDEO_URL "url"
+#define VIMEO_VIDEO_UPLOAD_DATE "upload_date"
+#define VIMEO_VIDEO_WIDTH "width"
+#define VIMEO_VIDEO_HEIGHT "height"
+#define VIMEO_VIDEO_DURATION "duration"
+#define VIMEO_VIDEO_OWNER "owner"
+#define VIMEO_VIDEO_THUMBNAIL "thumbnail"
+
+#define VIMEO_VIDEO_OWNER_NAME VIMEO_VIDEO_OWNER "_realname"
+
+typedef struct _GVimeo GVimeo;
+typedef struct _GVimeoPrivate GVimeoPrivate;
+
+struct _GVimeo {
+
+ GObject parent;
+
+ /*< private >*/
+ GVimeoPrivate *priv;
+};
+
+typedef struct _GVimeoClass GVimeoClass;
+
+struct _GVimeoClass {
+
+ GObjectClass parent_class;
+
+};
+
+typedef void (*GVimeoVideoSearchCb) (GVimeo *vimeo,
+ GList *videolist,
+ gpointer user_data);
+
+typedef void (*GVimeoURLCb) (gchar *url, gpointer user_data);
+
+GType g_vimeo_get_type (void);
+
+GVimeo *g_vimeo_new (const gchar *api_key, const gchar *auth_secret);
+
+void g_vimeo_video_get_play_url (GVimeo *vimeo,
+ gint id,
+ GVimeoURLCb callback,
+ gpointer user_data);
+
+void g_vimeo_set_per_page (GVimeo *vimeo, gint per_page);
+
+void g_vimeo_videos_search (GVimeo *vimeo,
+ const gchar *text,
+ gint page,
+ GVimeoVideoSearchCb callback,
+ gpointer user_data);
+
+#endif /* _G_VIMEO_H_ */
diff --git a/src/media/youtube/Makefile.am b/src/media/youtube/Makefile.am
new file mode 100644
index 0000000..2384bb8
--- /dev/null
+++ b/src/media/youtube/Makefile.am
@@ -0,0 +1,40 @@
+#
+# Makefile.am
+#
+# Author: Iago Toral Quiroga <itoral igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlyoutube.la
+
+libgrlyoutube_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GRLNET_CFLAGS) \
+ $(XML_CFLAGS) \
+ $(GTHREAD_CFLAGS) \
+ $(GDATA_CFLAGS)
+
+libgrlyoutube_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GRLNET_LIBS) \
+ $(XML_LIBS) \
+ $(GTHREAD_LIBS) \
+ $(GDATA_LIBS)
+
+libgrlyoutube_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlyoutube_la_SOURCES = grl-youtube.c grl-youtube.h
+
+libdir = $(GRL_PLUGINS_DIR)
+youtubexmldir = $(GRL_PLUGINS_CONF_DIR)
+youtubexml_DATA = $(YOUTUBE_PLUGIN_ID).xml
+
+EXTRA_DIST = $(youtubexml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/media/youtube/TODO b/src/media/youtube/TODO
new file mode 100644
index 0000000..409835c
--- /dev/null
+++ b/src/media/youtube/TODO
@@ -0,0 +1,6 @@
+- Consider using libgdata.
+- Fix get_metadata() for standard feeds.
+- Consider implementing store() -> video upload.
+- Consider implementing configuration (personal account)
+ -> Could add new category for browsing (my videos)
+ -> would enable store() and remove() operations.
diff --git a/src/media/youtube/grl-youtube.c b/src/media/youtube/grl-youtube.c
new file mode 100644
index 0000000..c6dfd2e
--- /dev/null
+++ b/src/media/youtube/grl-youtube.c
@@ -0,0 +1,1630 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ * Copyright (C) 2011 Intel Corporation.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
+#include <net/grl-net.h>
+#include <gdata/gdata.h>
+#include <string.h>
+
+#include "grl-youtube.h"
+
+enum {
+ PROP_0,
+ PROP_SERVICE,
+};
+
+#define GRL_YOUTUBE_SOURCE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_YOUTUBE_SOURCE_TYPE, \
+ GrlYoutubeSourcePriv))
+
+/* --------- Logging -------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT youtube_log_domain
+GRL_LOG_DOMAIN_STATIC(youtube_log_domain);
+
+/* ----- Root categories ---- */
+
+#define YOUTUBE_ROOT_NAME "Youtube"
+
+#define ROOT_DIR_FEEDS_INDEX 0
+#define ROOT_DIR_CATEGORIES_INDEX 1
+
+#define YOUTUBE_FEEDS_ID "standard-feeds"
+#define YOUTUBE_FEEDS_NAME "Standard feeds"
+
+#define YOUTUBE_CATEGORIES_ID "categories"
+#define YOUTUBE_CATEGORIES_NAME "Categories"
+#define YOUTUBE_CATEGORIES_URL "http://gdata.youtube.com/schemas/2007/categories.cat"
+
+/* ----- Feeds categories ---- */
+
+#define YOUTUBE_TOP_RATED_ID (YOUTUBE_FEEDS_ID "/0")
+#define YOUTUBE_TOP_RATED_NAME "Top Rated"
+
+#define YOUTUBE_TOP_FAVS_ID (YOUTUBE_FEEDS_ID "/1")
+#define YOUTUBE_TOP_FAVS_NAME "Top Favorites"
+
+#define YOUTUBE_MOST_VIEWED_ID (YOUTUBE_FEEDS_ID "/2")
+#define YOUTUBE_MOST_VIEWED_NAME "Most Viewed"
+
+#define YOUTUBE_MOST_POPULAR_ID (YOUTUBE_FEEDS_ID "/3")
+#define YOUTUBE_MOST_POPULAR_NAME "Most Popular"
+
+#define YOUTUBE_MOST_RECENT_ID (YOUTUBE_FEEDS_ID "/4")
+#define YOUTUBE_MOST_RECENT_NAME "Most Recent"
+
+#define YOUTUBE_MOST_DISCUSSED_ID (YOUTUBE_FEEDS_ID "/5")
+#define YOUTUBE_MOST_DISCUSSED_NAME "Most Discussed"
+
+#define YOUTUBE_MOST_LINKED_ID (YOUTUBE_FEEDS_ID "/6")
+#define YOUTUBE_MOST_LINKED_NAME "Most Linked"
+
+#define YOUTUBE_MOST_RESPONDED_ID (YOUTUBE_FEEDS_ID "/7")
+#define YOUTUBE_MOST_RESPONDED_NAME "Most Responded"
+
+#define YOUTUBE_FEATURED_ID (YOUTUBE_FEEDS_ID "/8")
+#define YOUTUBE_FEATURED_NAME "Recently Featured"
+
+#define YOUTUBE_MOBILE_ID (YOUTUBE_FEEDS_ID "/9")
+#define YOUTUBE_MOBILE_NAME "Watch On Mobile"
+
+/* --- Other --- */
+
+#define YOUTUBE_MAX_CHUNK 50
+
+#define YOUTUBE_VIDEO_INFO_URL "http://www.youtube.com/get_video_info?video_id=%s"
+#define YOUTUBE_VIDEO_URL "http://www.youtube.com/get_video?video_id=%s&t=%s&asv="
+#define YOUTUBE_CATEGORY_URL "http://gdata.youtube.com/feeds/api/videos/-/%s?&start-index=%s&max-results=%s"
+#define YOUTUBE_WATCH_URL "http://www.youtube.com/watch?v="
+
+#define YOUTUBE_VIDEO_MIME "application/x-shockwave-flash"
+#define YOUTUBE_SITE_URL "www.youtube.com"
+
+
+/* --- Plugin information --- */
+
+#define PLUGIN_ID YOUTUBE_PLUGIN_ID
+
+#define SOURCE_ID "grl-youtube"
+#define SOURCE_NAME "Youtube"
+#define SOURCE_DESC "A source for browsing and searching Youtube videos"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+/* --- Data types --- */
+
+typedef void (*AsyncReadCbFunc) (gchar *data, gpointer user_data);
+
+typedef void (*BuildMediaFromEntryCbFunc) (GrlMedia *media, gpointer user_data);
+
+typedef struct {
+ gchar *id;
+ gchar *name;
+ guint count;
+} CategoryInfo;
+
+typedef struct {
+ GrlMediaSource *source;
+ guint operation_id;
+ const gchar *container_id;
+ GList *keys;
+ GrlMetadataResolutionFlags flags;
+ guint skip;
+ guint count;
+ GrlMediaSourceResultCb callback;
+ gpointer user_data;
+ guint error_code;
+ CategoryInfo *category_info;
+ guint emitted;
+ guint matches;
+ guint ref_count;
+} OperationSpec;
+
+typedef struct {
+ GDataService *service;
+ CategoryInfo *category_info;
+} CategoryCountCb;
+
+typedef struct {
+ AsyncReadCbFunc callback;
+ gchar *url;
+ gpointer user_data;
+} AsyncReadCb;
+
+typedef struct {
+ GrlMedia *media;
+ BuildMediaFromEntryCbFunc callback;
+ gpointer user_data;
+} SetMediaUrlAsyncReadCb;
+
+typedef enum {
+ YOUTUBE_MEDIA_TYPE_ROOT,
+ YOUTUBE_MEDIA_TYPE_FEEDS,
+ YOUTUBE_MEDIA_TYPE_CATEGORIES,
+ YOUTUBE_MEDIA_TYPE_FEED,
+ YOUTUBE_MEDIA_TYPE_CATEGORY,
+ YOUTUBE_MEDIA_TYPE_VIDEO,
+} YoutubeMediaType;
+
+struct _GrlYoutubeSourcePriv {
+ GDataService *service;
+
+ GrlNetWc *wc;
+};
+
+#define YOUTUBE_CLIENT_ID "grilo"
+
+static GrlYoutubeSource *grl_youtube_source_new (const gchar *api_key,
+ const gchar *client_id);
+
+static void grl_youtube_source_set_property (GObject *object,
+ guint propid,
+ const GValue *value,
+ GParamSpec *pspec);
+static void grl_youtube_source_finalize (GObject *object);
+
+gboolean grl_youtube_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+static const GList *grl_youtube_source_supported_keys (GrlMetadataSource *source);
+
+static const GList *grl_youtube_source_slow_keys (GrlMetadataSource *source);
+
+static void grl_youtube_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss);
+
+static void grl_youtube_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs);
+
+static void grl_youtube_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms);
+
+static gboolean grl_youtube_test_media_from_uri (GrlMediaSource *source,
+ const gchar *uri);
+
+static void grl_youtube_get_media_from_uri (GrlMediaSource *source,
+ GrlMediaSourceMediaFromUriSpec *mfus);
+
+static void build_directories (GDataService *service);
+static void compute_feed_counts (GDataService *service);
+static void compute_category_counts (GDataService *service);
+
+/* ==================== Global Data ================= */
+
+guint root_dir_size = 2;
+CategoryInfo root_dir[] = {
+ {YOUTUBE_FEEDS_ID, YOUTUBE_FEEDS_NAME, 10},
+ {YOUTUBE_CATEGORIES_ID, YOUTUBE_CATEGORIES_NAME, 0},
+ {NULL, NULL, 0}
+};
+
+CategoryInfo feeds_dir[] = {
+ {YOUTUBE_TOP_RATED_ID, YOUTUBE_TOP_RATED_NAME, 0},
+ {YOUTUBE_TOP_FAVS_ID, YOUTUBE_TOP_FAVS_NAME, 0},
+ {YOUTUBE_MOST_VIEWED_ID, YOUTUBE_MOST_VIEWED_NAME, 0},
+ {YOUTUBE_MOST_POPULAR_ID, YOUTUBE_MOST_POPULAR_NAME, 0},
+ {YOUTUBE_MOST_RECENT_ID, YOUTUBE_MOST_RECENT_NAME, 0},
+ {YOUTUBE_MOST_DISCUSSED_ID, YOUTUBE_MOST_DISCUSSED_NAME, 0},
+ {YOUTUBE_MOST_LINKED_ID, YOUTUBE_MOST_LINKED_NAME, 0},
+ {YOUTUBE_MOST_RESPONDED_ID, YOUTUBE_MOST_RESPONDED_NAME, 0},
+ {YOUTUBE_FEATURED_ID, YOUTUBE_FEATURED_NAME, 0},
+ {YOUTUBE_MOBILE_ID, YOUTUBE_MOBILE_NAME, 0},
+ {NULL, NULL, 0}
+};
+
+CategoryInfo *categories_dir = NULL;
+
+static GrlYoutubeSource *ytsrc = NULL;
+
+/* =================== Youtube Plugin =============== */
+
+gboolean
+grl_youtube_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ gchar *api_key;
+ GrlConfig *config;
+ gint config_count;
+
+ GRL_LOG_DOMAIN_INIT (youtube_log_domain, "youtube");
+
+ GRL_DEBUG ("youtube_plugin_init");
+
+ if (!configs) {
+ GRL_WARNING ("Configuration not provided! Cannot configure plugin.");
+ return FALSE;
+ }
+
+ config_count = g_list_length (configs);
+ if (config_count > 1) {
+ GRL_WARNING ("Provided %d configs, but will only use one", config_count);
+ }
+
+ config = GRL_CONFIG (configs->data);
+ api_key = grl_config_get_api_key (config);
+ if (!api_key) {
+ GRL_WARNING ("Missing API Key, cannot configure Youtube plugin");
+ return FALSE;
+ }
+
+ /* libgdata needs this */
+ if (!g_thread_supported()) {
+ g_thread_init (NULL);
+ }
+
+ GrlYoutubeSource *source =
+ grl_youtube_source_new (api_key, YOUTUBE_CLIENT_ID);
+
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+
+ g_free (api_key);
+
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_youtube_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Youtube GObject ================ */
+
+G_DEFINE_TYPE (GrlYoutubeSource, grl_youtube_source, GRL_TYPE_MEDIA_SOURCE);
+
+static GrlYoutubeSource *
+grl_youtube_source_new (const gchar *api_key, const gchar *client_id)
+{
+ GRL_DEBUG ("grl_youtube_source_new");
+
+ GrlYoutubeSource *source;
+ GDataYouTubeService *service;
+
+ service = gdata_youtube_service_new (api_key, client_id);
+ if (!service) {
+ GRL_WARNING ("Failed to initialize gdata service");
+ return NULL;
+ }
+
+ /* Use auto-split mode because Youtube fails for queries
+ that request more than YOUTUBE_MAX_CHUNK results */
+ source = GRL_YOUTUBE_SOURCE (g_object_new (GRL_YOUTUBE_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ "auto-split-threshold",
+ YOUTUBE_MAX_CHUNK,
+ "yt-service", service,
+ NULL));
+
+ ytsrc = source;
+
+ /* Build browse content hierarchy:
+ - Query Youtube for available categories
+ - Compute category childcounts
+ We only need to do this once */
+ if (!categories_dir) {
+ build_directories (GDATA_SERVICE (service));
+ }
+
+ return source;
+}
+
+static void
+grl_youtube_source_class_init (GrlYoutubeSourceClass * klass)
+{
+ GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ source_class->search = grl_youtube_source_search;
+ source_class->browse = grl_youtube_source_browse;
+ source_class->metadata = grl_youtube_source_metadata;
+ source_class->test_media_from_uri = grl_youtube_test_media_from_uri;
+ source_class->media_from_uri = grl_youtube_get_media_from_uri;
+ metadata_class->supported_keys = grl_youtube_source_supported_keys;
+ metadata_class->slow_keys = grl_youtube_source_slow_keys;
+ gobject_class->set_property = grl_youtube_source_set_property;
+ gobject_class->finalize = grl_youtube_source_finalize;
+
+ g_object_class_install_property (gobject_class,
+ PROP_SERVICE,
+ g_param_spec_object ("yt-service",
+ "youtube-service",
+ "gdata youtube service object",
+ GDATA_TYPE_YOUTUBE_SERVICE,
+ G_PARAM_WRITABLE
+ | G_PARAM_CONSTRUCT_ONLY
+ | G_PARAM_STATIC_NAME));
+
+ g_type_class_add_private (klass, sizeof (GrlYoutubeSourcePriv));
+}
+
+static void
+grl_youtube_source_init (GrlYoutubeSource *source)
+{
+ source->priv = GRL_YOUTUBE_SOURCE_GET_PRIVATE (source);
+}
+
+static void
+grl_youtube_source_set_property (GObject *object,
+ guint propid,
+ const GValue *value,
+ GParamSpec *pspec)
+
+{
+ switch (propid) {
+ case PROP_SERVICE: {
+ GrlYoutubeSource *self;
+ self = GRL_YOUTUBE_SOURCE (object);
+ self->priv->service = g_value_get_object (value);
+ break;
+ }
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
+ }
+}
+
+static void
+grl_youtube_source_finalize (GObject *object)
+{
+ GrlYoutubeSource *self;
+
+ self = GRL_YOUTUBE_SOURCE (object);
+
+ if (self->priv->wc)
+ g_object_unref (self->priv->wc);
+
+ if (self->priv->service)
+ g_object_unref (self->priv->service);
+
+ G_OBJECT_CLASS (grl_youtube_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static OperationSpec *
+operation_spec_new ()
+{
+ GRL_DEBUG ("Allocating new spec");
+ OperationSpec *os = g_slice_new0 (OperationSpec);
+ os->ref_count = 1;
+ return os;
+}
+
+static void
+operation_spec_unref (OperationSpec *os)
+{
+ os->ref_count--;
+ if (os->ref_count == 0) {
+ g_slice_free (OperationSpec, os);
+ GRL_DEBUG ("freeing spec");
+ }
+}
+
+static void
+operation_spec_ref (OperationSpec *os)
+{
+ GRL_DEBUG ("Reffing spec");
+ os->ref_count++;
+}
+
+inline static GrlNetWc *
+get_wc ()
+{
+ if (ytsrc && !ytsrc->priv->wc)
+ ytsrc->priv->wc = grl_net_wc_new ();
+
+ return ytsrc->priv->wc;
+}
+
+static void
+read_done_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ AsyncReadCb *arc = (AsyncReadCb *) user_data;
+ GError *wc_error = NULL;
+ gchar *content = NULL;
+
+ grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res,
+ &content,
+ NULL,
+ &wc_error);
+ if (wc_error) {
+ GRL_WARNING ("Failed to open '%s': %s", arc->url, wc_error->message);
+ arc->callback (NULL, arc->user_data);
+ g_error_free (wc_error);
+ } else {
+ arc->callback (content, arc->user_data);
+ }
+ g_free (arc->url);
+ g_slice_free (AsyncReadCb, arc);
+}
+
+static void
+read_url_async (const gchar *url,
+ AsyncReadCbFunc callback,
+ gpointer user_data)
+{
+ AsyncReadCb *arc;
+
+ arc = g_slice_new0 (AsyncReadCb);
+ arc->url = g_strdup (url);
+ arc->callback = callback;
+ arc->user_data = user_data;
+
+ GRL_DEBUG ("Opening async '%s'", url);
+ grl_net_wc_request_async (get_wc (),
+ url,
+ NULL,
+ read_done_cb,
+ arc);
+}
+
+static void
+set_media_url_async_read_cb (gchar *data, gpointer user_data)
+{
+ SetMediaUrlAsyncReadCb *cb_data = (SetMediaUrlAsyncReadCb *) user_data;
+ gchar *url = NULL;
+ GMatchInfo *match_info = NULL;
+ static GRegex *regex = NULL;
+
+ if (!data) {
+ goto done;
+ }
+
+ if (regex == NULL) {
+ regex = g_regex_new (".*&fmt_url_map=([^&]+)&", G_REGEX_OPTIMIZE, 0, NULL);
+ }
+
+ /* Check if we find the url mapping */
+ g_regex_match (regex, data, 0, &match_info);
+ if (g_match_info_matches (match_info) == TRUE) {
+ gchar *url_map_escaped, *url_map;
+ gchar **mappings;
+
+ url_map_escaped = g_match_info_fetch (match_info, 1);
+ url_map = g_uri_unescape_string (url_map_escaped, NULL);
+ g_free (url_map_escaped);
+
+ mappings = g_strsplit (url_map, ",", 0);
+ g_free (url_map);
+
+ if (mappings != NULL) {
+ /* TODO: We get the URL from the first format available.
+ * We should provide the list of available urls or let the user
+ * configure preferred formats
+ */
+ gchar **mapping = g_strsplit (mappings[0], "|", 2);
+ url = g_strdup (mapping[1]);
+ g_strfreev (mapping);
+ }
+ } else {
+ GRL_DEBUG ("Format array not found, using token workaround");
+ gchar *token_start;
+ gchar *token_end;
+ gchar *token;
+ const gchar *video_id;
+
+ token_start = g_strrstr (data, "&token=");
+ if (!token_start) {
+ goto done;
+ }
+ token_start += 7;
+ token_end = strstr (token_start, "&");
+ token = g_strndup (token_start, token_end - token_start);
+
+ video_id = grl_media_get_id (cb_data->media);
+ url = g_strdup_printf (YOUTUBE_VIDEO_URL, video_id, token);
+ g_free (token);
+ }
+
+ done:
+ if (url) {
+ grl_media_set_url (cb_data->media, url);
+ g_free (url);
+ }
+
+ cb_data->callback (cb_data->media, cb_data->user_data);
+
+ g_free (cb_data);
+}
+
+static void
+set_media_url (GrlMedia *media,
+ BuildMediaFromEntryCbFunc callback,
+ gpointer user_data)
+{
+ const gchar *video_id;
+ gchar *video_info_url;
+ SetMediaUrlAsyncReadCb *set_media_url_async_read_data;
+
+ /* The procedure to get the video url is:
+ * 1) Read the video info URL using the video id (async operation)
+ * 2) In the video info page, there should be an array of supported formats
+ * and their corresponding URLs, right now we just use the first one we get.
+ * (see set_media_url_async_read_cb).
+ * TODO: we should be able to provide various urls or at least
+ * select preferred formats via configuration
+ * TODO: we should set mime-type accordingly to the format selected
+ * 3) As a workaround in case no format array is found we get the video token
+ * and figure out the url of the video using the video id and the token.
+ */
+
+ video_id = grl_media_get_id (media);
+ video_info_url = g_strdup_printf (YOUTUBE_VIDEO_INFO_URL, video_id);
+
+ set_media_url_async_read_data = g_new0 (SetMediaUrlAsyncReadCb, 1);
+ set_media_url_async_read_data->media = media;
+ set_media_url_async_read_data->callback = callback;
+ set_media_url_async_read_data->user_data = user_data;
+
+ read_url_async (video_info_url,
+ set_media_url_async_read_cb,
+ set_media_url_async_read_data);
+
+ g_free (video_info_url);
+}
+
+static void
+build_media_from_entry (GrlMedia *content,
+ GDataEntry *entry,
+ const GList *keys,
+ BuildMediaFromEntryCbFunc callback,
+ gpointer user_data)
+{
+ GDataYouTubeVideo *video;
+ GrlMedia *media;
+ GList *iter;
+ gboolean need_url = FALSE;
+
+ if (!content) {
+ media = grl_media_video_new ();
+ } else {
+ media = content;
+ }
+
+ video = GDATA_YOUTUBE_VIDEO (entry);
+
+ /* Make sure we set the media id in any case */
+ if (!grl_media_get_id (media)) {
+ grl_media_set_id (media, gdata_youtube_video_get_video_id (video));
+ }
+
+ iter = (GList *) keys;
+ while (iter) {
+ if (iter->data == GRL_METADATA_KEY_TITLE) {
+ grl_media_set_title (media, gdata_entry_get_title (entry));
+ } else if (iter->data == GRL_METADATA_KEY_DESCRIPTION) {
+ grl_media_set_description (media,
+ gdata_youtube_video_get_description (video));
+ } else if (iter->data == GRL_METADATA_KEY_THUMBNAIL) {
+ GList *thumb_list;
+ thumb_list = gdata_youtube_video_get_thumbnails (video);
+ if (thumb_list) {
+ GDataMediaThumbnail *thumbnail;
+ thumbnail = GDATA_MEDIA_THUMBNAIL (thumb_list->data);
+ grl_media_set_thumbnail (media,
+ gdata_media_thumbnail_get_uri (thumbnail));
+ }
+ } else if (iter->data == GRL_METADATA_KEY_DATE) {
+ GTimeVal date;
+ gchar *date_str;
+#ifdef GDATA_API_SUBJECT_TO_CHANGE
+ gint64 published = gdata_entry_get_published (entry);
+ date.tv_sec = (glong) published;
+#else
+ gdata_entry_get_published (entry, &date);
+#endif
+ if (date.tv_sec != 0 || date.tv_usec != 0) {
+ date_str = g_time_val_to_iso8601 (&date);
+ grl_media_set_date (media, date_str);
+ g_free (date_str);
+ }
+ } else if (iter->data == GRL_METADATA_KEY_DURATION) {
+ grl_media_set_duration (media, gdata_youtube_video_get_duration (video));
+ } else if (iter->data == GRL_METADATA_KEY_MIME) {
+ grl_media_set_mime (media, YOUTUBE_VIDEO_MIME);
+ } else if (iter->data == GRL_METADATA_KEY_SITE) {
+ grl_media_set_site (media, gdata_youtube_video_get_player_uri (video));
+ } else if (iter->data == GRL_METADATA_KEY_EXTERNAL_URL) {
+ grl_media_set_external_url (media,
+ gdata_youtube_video_get_player_uri (video));
+ } else if (iter->data == GRL_METADATA_KEY_RATING) {
+ gdouble average;
+ gdata_youtube_video_get_rating (video, NULL, NULL, NULL, &average);
+ grl_media_set_rating (media, average, 5.00);
+ } else if (iter->data == GRL_METADATA_KEY_URL) {
+ /* This needs another query and will be resolved asynchronously p Q*/
+ need_url = TRUE;
+ } else if (iter->data == GRL_METADATA_KEY_EXTERNAL_PLAYER) {
+ GDataYouTubeContent *youtube_content;
+ youtube_content =
+ gdata_youtube_video_look_up_content (video,
+ "application/x-shockwave-flash");
+ if (youtube_content != NULL) {
+ GDataMediaContent *content = GDATA_MEDIA_CONTENT (youtube_content);
+ grl_media_set_external_player (media,
+ gdata_media_content_get_uri (content));
+ }
+ }
+ iter = g_list_next (iter);
+ }
+
+ if (need_url) {
+ /* URL resolution is async */
+ set_media_url (media, callback, user_data);
+ } else {
+ callback (media, user_data);
+ }
+}
+
+static void
+parse_categories (xmlDocPtr doc, xmlNodePtr node, GDataService *service)
+{
+ GRL_DEBUG ("parse_categories");
+
+ guint total = 0;
+ GList *all = NULL, *iter;
+ CategoryInfo *cat_info;
+ gchar *id;
+ guint index = 0;
+
+ while (node) {
+ cat_info = g_slice_new (CategoryInfo);
+ id = (gchar *) xmlGetProp (node, (xmlChar *) "term");
+ cat_info->id = g_strconcat (YOUTUBE_CATEGORIES_ID, "/", id, NULL);
+ cat_info->name = (gchar *) xmlGetProp (node, (xmlChar *) "label");
+ all = g_list_prepend (all, cat_info);
+ g_free (id);
+ node = node->next;
+ total++;
+ GRL_DEBUG (" Found category: '%d - %s'", index++, cat_info->name);
+ }
+
+ if (all) {
+ root_dir[ROOT_DIR_CATEGORIES_INDEX].count = total;
+ categories_dir = g_new0 (CategoryInfo, total + 1);
+ iter = all;
+ do {
+ cat_info = (CategoryInfo *) iter->data;
+ categories_dir[total - 1].id = cat_info->id ;
+ categories_dir[total - 1].name = cat_info->name;
+ categories_dir[total - 1].count = 0;
+ total--;
+ g_slice_free (CategoryInfo, cat_info);
+ iter = g_list_next (iter);
+ } while (iter);
+ g_list_free (all);
+
+ compute_category_counts (service);
+ }
+}
+
+static void
+build_categories_directory_read_cb (gchar *xmldata, gpointer user_data)
+{
+ xmlDocPtr doc;
+ xmlNodePtr node;
+
+ if (!xmldata) {
+ g_critical ("Failed to build category directory (1)");
+ return;
+ }
+
+ doc = xmlReadMemory (xmldata, strlen (xmldata), NULL, NULL,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ if (!doc) {
+ g_critical ("Failed to build category directory (2)");
+ goto free_resources;
+ }
+
+ node = xmlDocGetRootElement (doc);
+ if (!node) {
+ g_critical ("Failed to build category directory (3)");
+ goto free_resources;
+ }
+
+ if (xmlStrcmp (node->name, (const xmlChar *) "categories")) {
+ g_critical ("Failed to build category directory (4)");
+ goto free_resources;
+ }
+
+ node = node->xmlChildrenNode;
+ if (!node) {
+ g_critical ("Failed to build category directory (5)");
+ goto free_resources;
+ }
+
+ parse_categories (doc, node, GDATA_SERVICE (user_data));
+
+ free_resources:
+ xmlFreeDoc (doc);
+}
+
+static gint
+get_feed_type_from_id (const gchar *feed_id)
+{
+ gchar *tmp;
+ gchar *test;
+ gint feed_type;
+
+ tmp = g_strrstr (feed_id, "/");
+ if (!tmp) {
+ return -1;
+ }
+ tmp++;
+
+ feed_type = strtol (tmp, &test, 10);
+ if (*test != '\0') {
+ return -1;
+ }
+
+ return feed_type;
+}
+
+static const gchar *
+get_category_term_from_id (const gchar *category_id)
+{
+ gchar *term;
+ term = g_strrstr (category_id, "/");
+ if (!term) {
+ return NULL;
+ }
+ return ++term;
+}
+
+static gint
+get_category_index_from_id (const gchar *category_id)
+{
+ gint i;
+
+ for (i=0; i<root_dir[ROOT_DIR_CATEGORIES_INDEX].count; i++) {
+ if (!strcmp (categories_dir[i].id, category_id)) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static void
+item_count_cb (GObject *object, GAsyncResult *result, CategoryCountCb *cc)
+{
+ GRL_DEBUG ("item_count_cb");
+
+ GDataFeed *feed;
+ GError *error = NULL;
+
+ feed = gdata_service_query_finish (GDATA_SERVICE (cc->service),
+ result, &error);
+ if (error) {
+ GRL_WARNING ("Failed to compute count for category '%s': %s",
+ cc->category_info->id, error->message);
+ g_error_free (error);
+ } else if (feed) {
+ cc->category_info->count = gdata_feed_get_total_results (feed);
+ GRL_DEBUG ("Category '%s' - childcount: '%u'",
+ cc->category_info->id, cc->category_info->count);
+ }
+
+ if (feed) {
+ g_object_unref (feed);
+ }
+ g_slice_free (CategoryCountCb, cc);
+}
+
+static void
+compute_category_counts (GDataService *service)
+{
+ gint i;
+
+ GRL_DEBUG ("compute_category_counts");
+
+ for (i=0; i<root_dir[ROOT_DIR_CATEGORIES_INDEX].count; i++) {
+ GRL_DEBUG ("Computing chilcount for category '%s'", categories_dir[i].id);
+ GDataQuery *query = gdata_query_new_with_limits (NULL, 0, 1);
+ const gchar *category_term =
+ get_category_term_from_id (categories_dir[i].id);
+ gdata_query_set_categories (query, category_term);
+ CategoryCountCb *cc = g_slice_new (CategoryCountCb);
+ cc->service = service;
+ cc->category_info = &categories_dir[i];
+ gdata_youtube_service_query_videos_async (GDATA_YOUTUBE_SERVICE (service),
+ query,
+ NULL, NULL, NULL,
+ (GAsyncReadyCallback) item_count_cb,
+ cc);
+ g_object_unref (query);
+ }
+}
+
+static void
+compute_feed_counts (GDataService *service)
+{
+ gint i;
+ GRL_DEBUG ("compute_feed_counts");
+
+ for (i=0; i<root_dir[ROOT_DIR_FEEDS_INDEX].count; i++) {
+ GRL_DEBUG ("Computing chilcount for feed '%s'", feeds_dir[i].id);
+ gint feed_type = get_feed_type_from_id (feeds_dir[i].id);
+ GDataQuery *query = gdata_query_new_with_limits (NULL, 0, 1);
+ CategoryCountCb *cc = g_slice_new (CategoryCountCb);
+ cc->service = service;
+ cc->category_info = &feeds_dir[i];
+ gdata_youtube_service_query_standard_feed_async (GDATA_YOUTUBE_SERVICE (service),
+ feed_type,
+ query,
+ NULL, NULL, NULL,
+ (GAsyncReadyCallback) item_count_cb,
+ cc);
+ g_object_unref (query);
+ }
+}
+
+static void
+build_media_from_entry_metadata_cb (GrlMedia *media, gpointer user_data)
+{
+ GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
+ ms->callback (ms->source, media, ms->user_data, NULL);
+}
+
+static void
+build_media_from_entry_search_cb (GrlMedia *media, gpointer user_data)
+{
+ /*
+ * TODO: Async resolution of URL messes (or could mess) with the sorting,
+ * If we want to ensure a particular sorting or implement sorting
+ * mechanisms we should add code to handle that here so we emit items in
+ * the right order and not just when we got the URL resolved (would
+ * damage response time though).
+ */
+ OperationSpec *os = (OperationSpec *) user_data;
+ guint remaining;
+
+ if (os->emitted < os->count) {
+ remaining = os->count - os->emitted - 1;
+ os->callback (os->source,
+ os->operation_id,
+ media,
+ remaining,
+ os->user_data,
+ NULL);
+ if (remaining == 0) {
+ GRL_DEBUG ("Unreffing spec in build_media_from_entry_search_cb");
+ operation_spec_unref (os);
+ } else {
+ os->emitted++;
+ }
+ }
+}
+
+static void
+build_directories (GDataService *service)
+{
+ GRL_DEBUG ("build_drectories");
+
+ /* Parse category list from Youtube and compute category counts */
+ read_url_async (YOUTUBE_CATEGORIES_URL,
+ build_categories_directory_read_cb,
+ service);
+
+ /* Compute feed counts */
+ compute_feed_counts (service);
+}
+
+static void
+metadata_cb (GObject *object,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GRL_DEBUG ("metadata_cb");
+
+ GError *error = NULL;
+ GrlYoutubeSource *source;
+ GDataEntry *video;
+ GDataService *service;
+ GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
+
+ source = GRL_YOUTUBE_SOURCE (ms->source);
+ service = GDATA_SERVICE (source->priv->service);
+
+#ifdef GDATA_API_SUBJECT_TO_CHANGE
+ video = gdata_service_query_single_entry_finish (service, result, &error);
+#else
+ video =
+ GDATA_ENTRY (gdata_youtube_service_query_single_video_finish
+ (GDATA_YOUTUBE_SERVICE (service), result, &error));
+#endif
+ if (error) {
+ error->code = GRL_CORE_ERROR_METADATA_FAILED;
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ } else {
+ build_media_from_entry (ms->media, video, ms->keys,
+ build_media_from_entry_metadata_cb, ms);
+ }
+
+ if (video) {
+ g_object_unref (video);
+ }
+}
+
+static void
+search_progress_cb (GDataEntry *entry,
+ guint index,
+ guint count,
+ gpointer user_data)
+{
+ OperationSpec *os = (OperationSpec *) user_data;
+ if (index < count) {
+ /* Keep track of the items we got here. Due to the asynchronous
+ * nature of build_media_from_entry(), when search_cb is invoked
+ * we have to check if we got as many results as we requested or
+ * not, and handle that situation properly */
+ os->matches++;
+ build_media_from_entry (NULL, entry, os->keys,
+ build_media_from_entry_search_cb, os);
+ } else {
+ GRL_WARNING ("Invalid index/count received grom libgdata, ignoring result");
+ }
+
+ /* The entry will be freed when freeing the feed in search_cb */
+}
+
+static void
+search_cb (GObject *object, GAsyncResult *result, OperationSpec *os)
+{
+ GRL_DEBUG ("search_cb");
+
+ GDataFeed *feed;
+ GError *error = NULL;
+ gboolean need_extra_unref = FALSE;
+ GrlYoutubeSource *source = GRL_YOUTUBE_SOURCE (os->source);
+
+ feed = gdata_service_query_finish (source->priv->service, result, &error);
+ if (!error && feed) {
+ /* If we are browsing a category, update the count for it */
+ if (os->category_info) {
+ os->category_info->count = gdata_feed_get_total_results (feed);
+ }
+
+ /* Check if we got as many results as we requested */
+ if (os->matches < os->count) {
+ os->count = os->matches;
+ /* In case we are resolving URLs asynchronously, from now on
+ * results will be sent with appropriate remaining, but it can
+ * also be the case that we have sent all the results already
+ * and the last one was sent with remaining>0, in that case
+ * we should send a finishing message now. */
+ if (os->emitted == os->count) {
+ GRL_DEBUG ("sending finishing message");
+ os->callback (os->source, os->operation_id,
+ NULL, 0, os->user_data, NULL);
+ need_extra_unref = TRUE;
+ }
+ }
+ } else {
+ if (!error) {
+ error = g_error_new (GRL_CORE_ERROR,
+ os->error_code,
+ "Failed to obtain feed from Youtube");
+ } else {
+ error->code = os->error_code;
+ }
+ os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
+ g_error_free (error);
+ need_extra_unref = TRUE;
+ }
+
+ if (feed)
+ g_object_unref (feed);
+
+ GRL_DEBUG ("Unreffing spec in search_cb");
+ operation_spec_unref (os);
+ if (need_extra_unref) {
+ /* We did not free the spec in the emission callback, do it here */
+ GRL_DEBUG ("need extra spec unref in search_cb");
+ operation_spec_unref (os);
+ }
+}
+
+static gboolean
+is_category_container (const gchar *container_id)
+{
+ return g_str_has_prefix (container_id, YOUTUBE_CATEGORIES_ID "/");
+}
+
+static gboolean
+is_feeds_container (const gchar *container_id)
+{
+ return g_str_has_prefix (container_id, YOUTUBE_FEEDS_ID "/");
+}
+
+static YoutubeMediaType
+classify_media_id (const gchar *media_id)
+{
+ if (!media_id) {
+ return YOUTUBE_MEDIA_TYPE_ROOT;
+ } else if (!strcmp (media_id, YOUTUBE_FEEDS_ID)) {
+ return YOUTUBE_MEDIA_TYPE_FEEDS;
+ } else if (!strcmp (media_id, YOUTUBE_CATEGORIES_ID)) {
+ return YOUTUBE_MEDIA_TYPE_CATEGORIES;
+ } else if (is_category_container (media_id)) {
+ return YOUTUBE_MEDIA_TYPE_CATEGORY;
+ } else if (is_feeds_container (media_id)) {
+ return YOUTUBE_MEDIA_TYPE_FEED;
+ } else {
+ return YOUTUBE_MEDIA_TYPE_VIDEO;
+ }
+}
+
+static void
+set_category_childcount (GDataService *service,
+ GrlMediaBox *content,
+ CategoryInfo *dir,
+ guint index)
+{
+ gint childcount;
+ gboolean set_childcount = TRUE;
+ const gchar *container_id;
+
+ container_id = grl_media_get_id (GRL_MEDIA (content));
+
+ if (dir == NULL) {
+ /* Special case: we want childcount of root category */
+ childcount = root_dir_size;
+ } else if (!strcmp (dir[index].id, YOUTUBE_FEEDS_ID)) {
+ childcount = root_dir[ROOT_DIR_FEEDS_INDEX].count;
+ } else if (!strcmp (dir[index].id, YOUTUBE_CATEGORIES_ID)) {
+ childcount = root_dir[ROOT_DIR_CATEGORIES_INDEX].count;
+ } else if (is_feeds_container (container_id)) {
+ gint feed_index = get_feed_type_from_id (container_id);
+ if (feed_index >= 0) {
+ childcount = feeds_dir[feed_index].count;
+ } else {
+ set_childcount = FALSE;
+ }
+ } else if (is_category_container (container_id)) {
+ gint cat_index = get_category_index_from_id (container_id);
+ if (cat_index >= 0) {
+ childcount = categories_dir[cat_index].count;
+ } else {
+ set_childcount = FALSE;
+ }
+ } else {
+ set_childcount = FALSE;
+ }
+
+ if (set_childcount) {
+ grl_media_box_set_childcount (content, childcount);
+ }
+}
+
+static GrlMedia *
+produce_container_from_directory (GDataService *service,
+ GrlMedia *media,
+ CategoryInfo *dir,
+ guint index)
+{
+ GrlMedia *content;
+
+ if (!media) {
+ /* Create mode */
+ content = grl_media_box_new ();
+ } else {
+ /* Update mode */
+ content = media;
+ }
+
+ if (!dir) {
+ grl_media_set_id (content, NULL);
+ grl_media_set_title (content, YOUTUBE_ROOT_NAME);
+ } else {
+ grl_media_set_id (content, dir[index].id);
+ grl_media_set_title (content, dir[index].name);
+ }
+ grl_media_set_site (content, YOUTUBE_SITE_URL);
+ set_category_childcount (service, GRL_MEDIA_BOX (content), dir, index);
+
+ return content;
+}
+
+static void
+produce_from_directory (CategoryInfo *dir, guint dir_size, OperationSpec *os)
+{
+ GRL_DEBUG ("produce_from_directory");
+
+ guint index, remaining;
+
+ /* Youtube's first index is 1, but the directories start at 0 */
+ os->skip--;
+
+ if (os->skip >= dir_size) {
+ /* No results */
+ os->callback (os->source,
+ os->operation_id,
+ NULL,
+ 0,
+ os->user_data,
+ NULL);
+ operation_spec_unref (os);
+ } else {
+ index = os->skip;
+ remaining = MIN (dir_size - os->skip, os->count);
+
+ do {
+ GDataService *service = GRL_YOUTUBE_SOURCE (os->source)->priv->service;
+
+ GrlMedia *content =
+ produce_container_from_directory (service, NULL, dir, index);
+
+ remaining--;
+ index++;
+
+ os->callback (os->source,
+ os->operation_id,
+ content,
+ remaining,
+ os->user_data,
+ NULL);
+
+ if (remaining == 0) {
+ operation_spec_unref (os);
+ }
+ } while (remaining > 0);
+ }
+}
+
+static void
+produce_from_feed (OperationSpec *os)
+{
+ GError *error = NULL;
+ gint feed_type;
+ GDataQuery *query;
+ GDataService *service;
+
+ feed_type = get_feed_type_from_id (os->container_id);
+
+ if (feed_type < 0) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Invalid feed id: %s", os->container_id);
+ os->callback (os->source,
+ os->operation_id,
+ NULL,
+ 0,
+ os->user_data,
+ error);
+ g_error_free (error);
+ operation_spec_unref (os);
+ return;
+ }
+ /* OPERATION_SPEC_REF_RATIONALE
+ * Depending on wether the URL has been requested, metadata resolution
+ * for each item in the result set may or may not be asynchronous.
+ * We cannot free the spec in search_cb because that may be called
+ * before the asynchronous URL resolution is finished, and we cannot
+ * do it in build_media_from_entry_search_cb either, because in the
+ * synchronous case (when we do not request URL) search_cb will
+ * be invoked after it.
+ * Thus, the solution is to increase the reference count here and
+ * have both places unreffing the spec, that way, no matter which
+ * is invoked last, the spec will be freed only once. */
+ operation_spec_ref (os);
+
+ service = GRL_YOUTUBE_SOURCE (os->source)->priv->service;
+ query = gdata_query_new_with_limits (NULL , os->skip, os->count);
+ os->category_info = &feeds_dir[feed_type];
+ gdata_youtube_service_query_standard_feed_async (GDATA_YOUTUBE_SERVICE (service),
+ feed_type,
+ query,
+ NULL,
+ search_progress_cb,
+ os,
+ (GAsyncReadyCallback) search_cb,
+ os);
+ g_object_unref (query);
+}
+
+static void
+produce_from_category (OperationSpec *os)
+{
+ GError *error = NULL;
+ GDataQuery *query;
+ GDataService *service;
+ const gchar *category_term;
+ gint category_index;
+
+ category_term = get_category_term_from_id (os->container_id);
+ category_index = get_category_index_from_id (os->container_id);
+
+ if (!category_term) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_BROWSE_FAILED,
+ "Invalid category id: %s", os->container_id);
+ os->callback (os->source,
+ os->operation_id,
+ NULL,
+ 0,
+ os->user_data,
+ error);
+ g_error_free (error);
+ operation_spec_unref (os);
+ return;
+ }
+
+ /* Look for OPERATION_SPEC_REF_RATIONALE for details */
+ operation_spec_ref (os);
+
+ service = GRL_YOUTUBE_SOURCE (os->source)->priv->service;
+ query = gdata_query_new_with_limits (NULL , os->skip, os->count);
+ os->category_info = &categories_dir[category_index];
+ gdata_query_set_categories (query, category_term);
+ gdata_youtube_service_query_videos_async (GDATA_YOUTUBE_SERVICE (service),
+ query,
+ NULL,
+ search_progress_cb,
+ os,
+ (GAsyncReadyCallback) search_cb,
+ os);
+ g_object_unref (query);
+}
+
+static gchar *
+get_video_id_from_url (const gchar *url)
+{
+ gchar *marker, *end, *video_id;
+
+ if (url == NULL) {
+ return NULL;
+ }
+
+ marker = strstr (url, YOUTUBE_WATCH_URL);
+ if (!marker) {
+ return NULL;
+ }
+
+ marker += strlen (YOUTUBE_WATCH_URL);
+
+ end = marker;
+ while (*end != '\0' && *end != '&') {
+ end++;
+ }
+
+ video_id = g_strndup (marker, end - marker);
+
+ return video_id;
+}
+
+static void
+build_media_from_entry_media_from_uri_cb (GrlMedia *media, gpointer user_data)
+{
+ GrlMediaSourceMediaFromUriSpec *mfus =
+ (GrlMediaSourceMediaFromUriSpec *) user_data;
+ mfus->callback (mfus->source, media, mfus->user_data, NULL);
+}
+
+static void
+media_from_uri_cb (GObject *object, GAsyncResult *result, gpointer user_data)
+{
+ GError *error = NULL;
+ GrlYoutubeSource *source;
+ GDataEntry *video;
+ GDataService *service;
+ GrlMediaSourceMediaFromUriSpec *mfus =
+ (GrlMediaSourceMediaFromUriSpec *) user_data;
+
+ source = GRL_YOUTUBE_SOURCE (mfus->source);
+ service = GDATA_SERVICE (source->priv->service);
+
+#ifdef GDATA_API_SUBJECT_TO_CHANGE
+ video = gdata_service_query_single_entry_finish (service, result, &error);
+#else
+ video =
+ GDATA_ENTRY (gdata_youtube_service_query_single_video_finish
+ (GDATA_YOUTUBE_SERVICE (service), result, &error));
+#endif
+
+ if (error) {
+ error->code = GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED;
+ mfus->callback (mfus->source, NULL, mfus->user_data, error);
+ g_error_free (error);
+ } else {
+ build_media_from_entry (NULL, video, mfus->keys,
+ build_media_from_entry_media_from_uri_cb,
+ mfus);
+ }
+
+ if (video) {
+ g_object_unref (video);
+ }
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_youtube_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
+ GRL_METADATA_KEY_TITLE,
+ GRL_METADATA_KEY_URL,
+ GRL_METADATA_KEY_EXTERNAL_URL,
+ GRL_METADATA_KEY_DESCRIPTION,
+ GRL_METADATA_KEY_DURATION,
+ GRL_METADATA_KEY_DATE,
+ GRL_METADATA_KEY_THUMBNAIL,
+ GRL_METADATA_KEY_MIME,
+ GRL_METADATA_KEY_CHILDCOUNT,
+ GRL_METADATA_KEY_SITE,
+ GRL_METADATA_KEY_RATING,
+ GRL_METADATA_KEY_EXTERNAL_PLAYER,
+ NULL);
+ }
+ return keys;
+}
+
+static const GList *
+grl_youtube_source_slow_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_youtube_source_search (GrlMediaSource *source,
+ GrlMediaSourceSearchSpec *ss)
+{
+ OperationSpec *os;
+ GDataQuery *query;
+
+ GRL_DEBUG ("grl_youtube_source_search (%u, %u)", ss->skip, ss->count);
+
+ os = operation_spec_new ();
+ os->source = source;
+ os->operation_id = ss->search_id;
+ os->keys = ss->keys;
+ os->skip = ss->skip + 1;
+ os->count = ss->count;
+ os->callback = ss->callback;
+ os->user_data = ss->user_data;
+ os->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
+
+ /* Look for OPERATION_SPEC_REF_RATIONALE for details */
+ operation_spec_ref (os);
+
+ query = gdata_query_new_with_limits (ss->text, os->skip, os->count);
+ gdata_youtube_service_query_videos_async (GDATA_YOUTUBE_SERVICE (GRL_YOUTUBE_SOURCE (source)->priv->service),
+ query,
+ NULL,
+ search_progress_cb,
+ os,
+ (GAsyncReadyCallback) search_cb,
+ os);
+ g_object_unref (query);
+}
+
+static void
+grl_youtube_source_browse (GrlMediaSource *source,
+ GrlMediaSourceBrowseSpec *bs)
+{
+ OperationSpec *os;
+ const gchar *container_id;
+
+ GRL_DEBUG ("grl_youtube_source_browse: %s", grl_media_get_id (bs->container));
+
+ container_id = grl_media_get_id (bs->container);
+
+ os = operation_spec_new ();
+ os->source = bs->source;
+ os->operation_id = bs->browse_id;
+ os->container_id = container_id;
+ os->keys = bs->keys;
+ os->flags = bs->flags;
+ os->skip = bs->skip + 1;
+ os->count = bs->count;
+ os->callback = bs->callback;
+ os->user_data = bs->user_data;
+ os->error_code = GRL_CORE_ERROR_BROWSE_FAILED;
+
+ switch (classify_media_id (container_id))
+ {
+ case YOUTUBE_MEDIA_TYPE_ROOT:
+ produce_from_directory (root_dir, root_dir_size, os);
+ break;
+ case YOUTUBE_MEDIA_TYPE_FEEDS:
+ produce_from_directory (feeds_dir,
+ root_dir[ROOT_DIR_FEEDS_INDEX].count, os);
+ break;
+ case YOUTUBE_MEDIA_TYPE_CATEGORIES:
+ produce_from_directory (categories_dir,
+ root_dir[ROOT_DIR_CATEGORIES_INDEX].count, os);
+ break;
+ case YOUTUBE_MEDIA_TYPE_FEED:
+ produce_from_feed (os);
+ break;
+ case YOUTUBE_MEDIA_TYPE_CATEGORY:
+ produce_from_category (os);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static void
+grl_youtube_source_metadata (GrlMediaSource *source,
+ GrlMediaSourceMetadataSpec *ms)
+{
+ YoutubeMediaType media_type;
+ const gchar *id;
+ GDataService *service;
+ GError *error = NULL;
+ GrlMedia *media = NULL;
+
+ GRL_DEBUG ("grl_youtube_source_metadata");
+
+ id = grl_media_get_id (ms->media);
+ media_type = classify_media_id (id);
+ service = GRL_YOUTUBE_SOURCE (source)->priv->service;
+
+ switch (media_type) {
+ case YOUTUBE_MEDIA_TYPE_ROOT:
+ media = produce_container_from_directory (service, ms->media, NULL, 0);
+ break;
+ case YOUTUBE_MEDIA_TYPE_FEEDS:
+ media = produce_container_from_directory (service, ms->media, root_dir, 0);
+ break;
+ case YOUTUBE_MEDIA_TYPE_CATEGORIES:
+ media = produce_container_from_directory (service, ms->media, root_dir, 1);
+ break;
+ case YOUTUBE_MEDIA_TYPE_FEED:
+ {
+ gint index = get_feed_type_from_id (id);
+ if (index >= 0) {
+ media = produce_container_from_directory (service, ms->media, feeds_dir,
+ index);
+ } else {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Invalid feed id");
+ }
+ }
+ break;
+ case YOUTUBE_MEDIA_TYPE_CATEGORY:
+ {
+ gint index = get_category_index_from_id (id);
+ if (index >= 0) {
+ media = produce_container_from_directory (service, ms->media,
+ categories_dir, index);
+ } else {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_METADATA_FAILED,
+ "Invalid category id");
+ }
+ }
+ break;
+ case YOUTUBE_MEDIA_TYPE_VIDEO:
+ default:
+#ifdef GDATA_API_SUBJECT_TO_CHANGE
+ {
+ gchar *entryid = g_strconcat ("tag:youtube.com,2008:video:", id, NULL);
+ gdata_service_query_single_entry_async (service,
+ entryid,
+ NULL,
+ GDATA_TYPE_YOUTUBE_VIDEO,
+ NULL,
+ metadata_cb,
+ ms);
+ g_free (entryid);
+ }
+#else
+ gdata_youtube_service_query_single_video_async (GDATA_YOUTUBE_SERVICE (service),
+ NULL,
+ id,
+ NULL,
+ metadata_cb,
+ ms);
+#endif
+ break;
+ }
+
+ if (error) {
+ ms->callback (ms->source, ms->media, ms->user_data, error);
+ g_error_free (error);
+ } else if (media) {
+ ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ }
+}
+
+static gboolean
+grl_youtube_test_media_from_uri (GrlMediaSource *source, const gchar *uri)
+{
+ GRL_DEBUG ("grl_youtube_test_media_from_uri");
+
+ gchar *video_id;
+ gboolean ok;
+
+ video_id = get_video_id_from_url (uri);
+ ok = (video_id != NULL);
+ g_free (video_id);
+ return ok;
+}
+
+static void
+grl_youtube_get_media_from_uri (GrlMediaSource *source,
+ GrlMediaSourceMediaFromUriSpec *mfus)
+{
+ GRL_DEBUG ("grl_youtube_get_media_from_uri");
+
+ gchar *video_id;
+ GError *error;
+ GDataService *service;
+
+ video_id = get_video_id_from_url (mfus->uri);
+ if (video_id == NULL) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED,
+ "Cannot create media from '%s'", mfus->uri);
+ mfus->callback (source, NULL, mfus->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ service = GRL_YOUTUBE_SOURCE (source)->priv->service;
+
+#ifdef GDATA_API_SUBJECT_TO_CHANGE
+ gchar *entry_id = g_strconcat ("tag:youtube.com,2008:video:", video_id, NULL);
+ gdata_service_query_single_entry_async (service,
+ entry_id,
+ NULL,
+ GDATA_TYPE_YOUTUBE_VIDEO,
+ NULL,
+ media_from_uri_cb,
+ mfus);
+ g_free (entry_id);
+#else
+ gdata_youtube_service_query_single_video_async (GDATA_YOUTUBE_SERVICE (service),
+ NULL,
+ video_id,
+ NULL,
+ media_from_uri_cb,
+ mfus);
+#endif
+}
diff --git a/src/media/youtube/grl-youtube.h b/src/media/youtube/grl-youtube.h
new file mode 100644
index 0000000..dc88589
--- /dev/null
+++ b/src/media/youtube/grl-youtube.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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_YOUTUBE_SOURCE_H_
+#define _GRL_YOUTUBE_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_YOUTUBE_SOURCE_TYPE \
+ (grl_youtube_source_get_type ())
+
+#define GRL_YOUTUBE_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_YOUTUBE_SOURCE_TYPE, \
+ GrlYoutubeSource))
+
+#define GRL_IS_YOUTUBE_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_YOUTUBE_SOURCE_TYPE))
+
+#define GRL_YOUTUBE_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_YOUTUBE_SOURCE_TYPE, \
+ GrlYoutubeSourceClass))
+
+#define GRL_IS_YOUTUBE_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_YOUTUBE_SOURCE_TYPE))
+
+#define GRL_YOUTUBE_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_YOUTUBE_SOURCE_TYPE, \
+ GrlYoutubeSourceClass))
+
+typedef struct _GrlYoutubeSource GrlYoutubeSource;
+typedef struct _GrlYoutubeSourcePriv GrlYoutubeSourcePriv;
+
+struct _GrlYoutubeSource {
+
+ GrlMediaSource parent;
+ GrlYoutubeSourcePriv *priv;
+
+};
+
+typedef struct _GrlYoutubeSourceClass GrlYoutubeSourceClass;
+
+struct _GrlYoutubeSourceClass {
+
+ GrlMediaSourceClass parent_class;
+
+};
+
+GType grl_youtube_source_get_type (void);
+
+#endif /* _GRL_YOUTUBE_SOURCE_H_ */
diff --git a/src/media/youtube/grl-youtube.xml b/src/media/youtube/grl-youtube.xml
new file mode 100644
index 0000000..b874678
--- /dev/null
+++ b/src/media/youtube/grl-youtube.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>YouTube</name>
+ <description>A plugin for browsing and searching Youtube videos</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/metadata-store/Makefile.am b/src/metadata-store/Makefile.am
deleted file mode 100644
index 372f4a8..0000000
--- a/src/metadata-store/Makefile.am
+++ /dev/null
@@ -1,34 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Iago Toral Quiroga <itoral igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlmetadatastore.la
-
-libgrlmetadatastore_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(SQLITE_CFLAGS)
-
-libgrlmetadatastore_la_LIBADD = \
- $(DEPS_LIBS) \
- $(SQLITE_LIBS)
-
-libgrlmetadatastore_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlmetadatastore_la_SOURCES = grl-metadata-store.c grl-metadata-store.h
-
-libdir=$(GRL_PLUGINS_DIR)
-metadatastorexmldir = $(GRL_PLUGINS_CONF_DIR)
-metadatastorexml_DATA = $(METADATA_STORE_PLUGIN_ID).xml
-
-EXTRA_DIST = $(metadatastorexml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/metadata-store/grl-metadata-store.c b/src/metadata-store/grl-metadata-store.c
deleted file mode 100644
index f596585..0000000
--- a/src/metadata-store/grl-metadata-store.c
+++ /dev/null
@@ -1,650 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
-#include <sqlite3.h>
-#include <string.h>
-
-#include "grl-metadata-store.h"
-
-#define GRL_METADATA_STORE_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_METADATA_STORE_SOURCE_TYPE, \
- GrlMetadataStorePrivate))
-
-#define GRL_LOG_DOMAIN_DEFAULT metadata_store_log_domain
-GRL_LOG_DOMAIN_STATIC(metadata_store_log_domain);
-
-#define PLUGIN_ID METADATA_STORE_PLUGIN_ID
-
-#define SOURCE_ID "grl-metadata-store"
-#define SOURCE_NAME "Metadata Store"
-#define SOURCE_DESC "A plugin for storing extra metadata information"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-#define GRL_SQL_DB ".grl-metadata-store"
-
-#define GRL_SQL_CREATE_TABLE_STORE \
- "CREATE TABLE IF NOT EXISTS store (" \
- "source_id TEXT," \
- "media_id TEXT," \
- "play_count INTEGER," \
- "rating REAL," \
- "last_position INTEGER," \
- "last_played DATE)"
-
-#define GRL_SQL_GET_METADATA \
- "SELECT * FROM store " \
- "WHERE source_id='%s' AND media_id='%s' " \
- "LIMIT 1"
-
-#define GRL_SQL_UPDATE_METADATA \
- "UPDATE store SET %s " \
- "WHERE source_id=? AND media_id=?"
-
-#define GRL_SQL_INSERT_METADATA \
- "INSERT INTO store " \
- "(%s source_id, media_id) VALUES " \
- "(%s ?, ?)"
-
-struct _GrlMetadataStorePrivate {
- sqlite3 *db;
-};
-
-enum {
- STORE_SOURCE_ID = 0,
- STORE_MEDIA_ID,
- STORE_PLAY_COUNT,
- STORE_RATING,
- STORE_LAST_POSITION,
- STORE_LAST_PLAYED,
-};
-
-static GrlMetadataStoreSource *grl_metadata_store_source_new (void);
-
-static void grl_metadata_store_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs);
-
-static void grl_metadata_store_source_set_metadata (GrlMetadataSource *source,
- GrlMetadataSourceSetMetadataSpec *sms);
-
-static const GList *grl_metadata_store_source_supported_keys (GrlMetadataSource *source);
-
-static const GList *grl_metadata_store_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id);
-static const GList *grl_metadata_store_source_writable_keys (GrlMetadataSource *source);
-
-gboolean grl_metadata_store_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-
-/* =================== GrlMetadataStore Plugin =============== */
-
-gboolean
-grl_metadata_store_source_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (metadata_store_log_domain, "metadata-store");
-
- GRL_DEBUG ("grl_metadata_store_source_plugin_init");
-
- GrlMetadataStoreSource *source = grl_metadata_store_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_metadata_store_source_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== GrlMetadataStore GObject ================ */
-
-static GrlMetadataStoreSource *
-grl_metadata_store_source_new (void)
-{
- GRL_DEBUG ("grl_metadata_store_source_new");
- return g_object_new (GRL_METADATA_STORE_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_metadata_store_source_class_init (GrlMetadataStoreSourceClass * klass)
-{
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- metadata_class->supported_keys = grl_metadata_store_source_supported_keys;
- metadata_class->key_depends = grl_metadata_store_source_key_depends;
- metadata_class->writable_keys = grl_metadata_store_source_writable_keys;
- metadata_class->resolve = grl_metadata_store_source_resolve;
- metadata_class->set_metadata = grl_metadata_store_source_set_metadata;
-
- g_type_class_add_private (klass, sizeof (GrlMetadataStorePrivate));
-}
-
-static void
-grl_metadata_store_source_init (GrlMetadataStoreSource *source)
-{
- gint r;
- const gchar *home;
- gchar *db_path;
- gchar *sql_error = NULL;
-
- source->priv = GRL_METADATA_STORE_GET_PRIVATE (source);
-
- home = g_getenv ("HOME");
- if (!home) {
- GRL_WARNING ("$HOME not set, cannot open database");
- return;
- }
-
- GRL_DEBUG ("Opening database connection...");
- db_path = g_strconcat (home, G_DIR_SEPARATOR_S, GRL_SQL_DB, NULL);
- r = sqlite3_open (db_path, &source->priv->db);
- if (r) {
- g_critical ("Failed to open database '%s': %s",
- db_path, sqlite3_errmsg (source->priv->db));
- sqlite3_close (source->priv->db);
- return;
- }
- GRL_DEBUG (" OK");
-
- GRL_DEBUG ("Checking database tables...");
- r = sqlite3_exec (source->priv->db, GRL_SQL_CREATE_TABLE_STORE,
- NULL, NULL, &sql_error);
-
- if (r) {
- if (sql_error) {
- GRL_WARNING ("Failed to create database tables: %s", sql_error);
- sqlite3_free (sql_error);
- sql_error = NULL;
- } else {
- GRL_WARNING ("Failed to create database tables.");
- }
- sqlite3_close (source->priv->db);
- return;
- }
- GRL_DEBUG (" OK");
-
- g_free (db_path);
-}
-
-G_DEFINE_TYPE (GrlMetadataStoreSource, grl_metadata_store_source,
- GRL_TYPE_METADATA_SOURCE);
-
-/* ======================= Utilities ==================== */
-
-static sqlite3_stmt *
-query_metadata_store (sqlite3 *db,
- const gchar *source_id,
- const gchar *media_id)
-{
- gint r;
- sqlite3_stmt *sql_stmt = NULL;
- gchar *sql;
-
- GRL_DEBUG ("get_metadata");
-
- sql = g_strdup_printf (GRL_SQL_GET_METADATA, source_id, media_id);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to get metadata: %s", sqlite3_errmsg (db));
- return NULL;
- }
-
- return sql_stmt;
-}
-
-static void
-fill_metadata (GrlMedia *media, GList *keys, sqlite3_stmt *stmt)
-{
- GList *iter;
- gint play_count, last_position;
- gdouble rating;
- gchar *last_played;
- gint r;
-
- while ((r = sqlite3_step (stmt)) == SQLITE_BUSY);
-
- if (r != SQLITE_ROW) {
- /* No info in DB for this item, bail out silently */
- sqlite3_finalize (stmt);
- return;
- }
-
- iter = keys;
- while (iter) {
- if (iter->data == GRL_METADATA_KEY_PLAY_COUNT) {
- play_count = sqlite3_column_int (stmt, STORE_PLAY_COUNT);
- grl_media_set_play_count (media, play_count);
- } else if (iter->data == GRL_METADATA_KEY_RATING) {
- rating = sqlite3_column_double (stmt, STORE_RATING);
- grl_media_set_rating (media, rating, 5.00);
- } else if (iter->data == GRL_METADATA_KEY_LAST_PLAYED) {
- last_played = (gchar *) sqlite3_column_text (stmt, STORE_LAST_PLAYED);
- grl_media_set_last_played (media, last_played);
- } else if (iter->data == GRL_METADATA_KEY_LAST_POSITION) {
- last_position = sqlite3_column_int (stmt, STORE_LAST_POSITION);
- grl_media_set_last_position (media, last_position);
- }
- iter = g_list_next (iter);
- }
-
- sqlite3_finalize (stmt);
-}
-
-static const gchar *
-get_column_name_from_key_id (GrlKeyID key_id)
-{
- static const gchar *col_names[] = {"rating", "last_played", "last_position",
- "play_count"};
- if (key_id == GRL_METADATA_KEY_RATING) {
- return col_names[0];
- } else if (key_id == GRL_METADATA_KEY_LAST_PLAYED) {
- return col_names[1];
- } else if (key_id == GRL_METADATA_KEY_LAST_POSITION) {
- return col_names[2];
- } else if (key_id == GRL_METADATA_KEY_PLAY_COUNT) {
- return col_names[3];
- } else {
- return NULL;
- }
-}
-
-static gboolean
-bind_and_exec (sqlite3 *db,
- const gchar *sql,
- const gchar *source_id,
- const gchar *media_id,
- GList *col_names,
- GList *keys,
- GrlMedia *media)
-{
- gint r;
- const gchar *char_value;
- gint int_value;
- double double_value;
- GList *iter_names, *iter_keys;
- guint count;
- sqlite3_stmt *stmt;
-
- /* Create statement from sql */
- GRL_DEBUG ("%s", sql);
- r = sqlite3_prepare_v2 (db, sql, strlen (sql), &stmt, NULL);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to update metadata for '%s - %s': %s",
- source_id, media_id, sqlite3_errmsg (db));
- sqlite3_finalize (stmt);
- return FALSE;
- }
-
- /* Bind column values */
- count = 1;
- iter_names = col_names;
- iter_keys = keys;
- while (iter_names) {
- if (iter_names->data) {
- if (iter_keys->data == GRL_METADATA_KEY_RATING) {
- double_value = grl_media_get_rating (media);
- sqlite3_bind_double (stmt, count, double_value);
- } else if (iter_keys->data == GRL_METADATA_KEY_PLAY_COUNT) {
- int_value = grl_media_get_play_count (media);
- sqlite3_bind_int (stmt, count, int_value);
- } else if (iter_keys->data == GRL_METADATA_KEY_LAST_POSITION) {
- int_value = grl_media_get_last_position (media);
- sqlite3_bind_int (stmt, count, int_value);
- } else if (iter_keys->data == GRL_METADATA_KEY_LAST_PLAYED) {
- char_value = grl_media_get_last_played (media);
- sqlite3_bind_text (stmt, count, char_value, -1, SQLITE_STATIC);
- }
- count++;
- }
- iter_keys = g_list_next (iter_keys);
- iter_names = g_list_next (iter_names);
- }
-
- sqlite3_bind_text (stmt, count++, source_id, -1, SQLITE_STATIC);
- sqlite3_bind_text (stmt, count++, media_id, -1, SQLITE_STATIC);
-
- /* execute query */
- while ((r = sqlite3_step (stmt)) == SQLITE_BUSY);
-
- sqlite3_finalize (stmt);
-
- return (r == SQLITE_DONE);
-}
-
-static gboolean
-prepare_and_exec_update (sqlite3 *db,
- const gchar *source_id,
- const gchar *media_id,
- GList *col_names,
- GList *keys,
- GrlMedia *media)
-{
- gchar *sql;
- gint r;
- GList *iter_names;
- GString *sql_buf;
- gchar *sql_set;
- guint count;
-
- GRL_DEBUG ("prepare_and_exec_update");
-
- /* Prepare sql "set" for update query */
- count = 0;
- sql_buf = g_string_new ("");
- iter_names = col_names;
- while (iter_names) {
- gchar *col_name = (gchar *) iter_names->data;
- if (col_name) {
- if (count > 0) {
- g_string_append (sql_buf, " AND ");
- }
- g_string_append_printf (sql_buf, "%s=?", col_name);
- count++;
- }
- iter_names = g_list_next (iter_names);
- }
- sql_set = g_string_free (sql_buf, FALSE);
-
- /* Execute query */
- sql = g_strdup_printf (GRL_SQL_UPDATE_METADATA, sql_set);
- r = bind_and_exec (db, sql, source_id, media_id, col_names, keys, media);
- g_free (sql);
- g_free (sql_set);
-
- return r;
-}
-
-static gboolean
-prepare_and_exec_insert (sqlite3 *db,
- const gchar *source_id,
- const gchar *media_id,
- GList *col_names,
- GList *keys,
- GrlMedia *media)
-{
- gchar *sql;
- gint r;
- GList *iter_names;
- GString *sql_buf_cols, *sql_buf_values;
- gchar *sql_cols, *sql_values;
-
- GRL_DEBUG ("prepare_and_exec_insert");
-
- /* Prepare sql for insert query */
- sql_buf_cols = g_string_new ("");
- sql_buf_values = g_string_new ("");
- iter_names = col_names;
- while (iter_names) {
- gchar *col_name = (gchar *) iter_names->data;
- if (col_name) {
- g_string_append_printf (sql_buf_cols, "%s, ", col_name);
- g_string_append (sql_buf_values, "?, ");
- }
- iter_names = g_list_next (iter_names);
- }
- sql_cols = g_string_free (sql_buf_cols, FALSE);
- sql_values = g_string_free (sql_buf_values, FALSE);
-
- /* Execute query */
- sql = g_strdup_printf (GRL_SQL_INSERT_METADATA, sql_cols, sql_values);
- r = bind_and_exec (db, sql, source_id, media_id, col_names, keys, media);
- g_free (sql);
- g_free (sql_cols);
- g_free (sql_values);
-
- return r;
-}
-
-static GList *
-write_keys (sqlite3 *db,
- const gchar *source_id,
- const gchar *media_id,
- GrlMetadataSourceSetMetadataSpec *sms,
- GError **error)
-{
- GList *col_names = NULL;
- GList *iter;
- GList *failed_keys = NULL;
- guint supported_keys = 0;
- gint r;
-
- /* Get DB column names for each key to be updated */
- iter = sms->keys;
- while (iter) {
- const gchar *col_name = get_column_name_from_key_id (iter->data);
- if (!col_name) {
- GRL_WARNING ("Key %" GRL_KEYID_FORMAT " is not supported for "
- "writing, ignoring...",
- iter->data);
- failed_keys = g_list_prepend (failed_keys, iter->data);
- } else {
- supported_keys++;
- }
- col_names = g_list_prepend (col_names, (gchar *) col_name);
- iter = g_list_next (iter);
- }
- col_names = g_list_reverse (col_names);
-
- if (supported_keys == 0) {
- GRL_WARNING ("Failed to update metadata, none of the specified "
- "keys is writable");
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_SET_METADATA_FAILED,
- "Failed to update metadata, "
- "specified keys are not writable");
- goto done;
- }
-
- r = prepare_and_exec_update (db,
- source_id,
- media_id,
- col_names,
- sms->keys,
- sms->media);
-
- if (!r) {
- GRL_WARNING ("Failed to update metadata for '%s - %s': %s",
- source_id, media_id, sqlite3_errmsg (db));
- g_list_free (failed_keys);
- failed_keys = g_list_copy (sms->keys);
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_SET_METADATA_FAILED,
- "Failed to update metadata");
- goto done;
- }
-
- if (sqlite3_changes (db) == 0) {
- /* We have to create the row */
- r = prepare_and_exec_insert (db,
- source_id,
- media_id,
- col_names,
- sms->keys,
- sms->media);
- }
-
- if (!r) {
- GRL_WARNING ("Failed to update metadata for '%s - %s': %s",
- source_id, media_id, sqlite3_errmsg (db));
- g_list_free (failed_keys);
- failed_keys = g_list_copy (sms->keys);
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_SET_METADATA_FAILED,
- "Failed to update metadata");
- goto done;
- }
-
- done:
- g_list_free (col_names);
- return failed_keys;
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_metadata_store_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_RATING,
- GRL_METADATA_KEY_PLAY_COUNT,
- GRL_METADATA_KEY_LAST_PLAYED,
- GRL_METADATA_KEY_LAST_POSITION,
- NULL);
- }
- return keys;
-}
-
-static const GList *
-grl_metadata_store_source_writable_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_RATING,
- GRL_METADATA_KEY_PLAY_COUNT,
- GRL_METADATA_KEY_LAST_PLAYED,
- GRL_METADATA_KEY_LAST_POSITION,
- NULL);
- }
- return keys;
-}
-
-static const GList *
-grl_metadata_store_source_key_depends (GrlMetadataSource *source,
- GrlKeyID key_id)
-{
- static GList *deps = NULL;
- if (!deps) {
- deps = grl_metadata_key_list_new (GRL_METADATA_KEY_ID, NULL);
- }
-
- if (key_id == GRL_METADATA_KEY_RATING ||
- key_id == GRL_METADATA_KEY_PLAY_COUNT ||
- key_id == GRL_METADATA_KEY_LAST_PLAYED ||
- key_id == GRL_METADATA_KEY_LAST_POSITION) {
- return deps;
- }
-
- return NULL;
-}
-
-static void
-grl_metadata_store_source_resolve (GrlMetadataSource *source,
- GrlMetadataSourceResolveSpec *rs)
-{
- GRL_DEBUG ("grl_metadata_store_source_resolve");
-
- const gchar *source_id, *media_id;
- sqlite3_stmt *stmt;
- GError *error = NULL;
-
- source_id = grl_media_get_source (rs->media);
- media_id = grl_media_get_id (rs->media);
-
- /* We need the source id */
- if (!source_id) {
- GRL_WARNING ("Failed to resolve metadata: source-id not available");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_RESOLVE_FAILED,
- "source-id not available, cannot resolve metadata.");
- rs->callback (rs->source, rs->media, rs->user_data, error);
- g_error_free (error);
- return;
- }
-
- /* Special case for root categories */
- if (!media_id) {
- media_id = "";
- }
-
- stmt = query_metadata_store (GRL_METADATA_STORE_SOURCE (source)->priv->db,
- source_id, media_id);
- if (stmt) {
- fill_metadata (rs->media, rs->keys, stmt);
- rs->callback (rs->source, rs->media, rs->user_data, NULL);
- } else {
- GRL_WARNING ("Failed to resolve metadata");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_RESOLVE_FAILED,
- "Failed to resolve metadata.");
- rs->callback (rs->source, rs->media, rs->user_data, error);
- g_error_free (error);
- }
-}
-
-static void
-grl_metadata_store_source_set_metadata (GrlMetadataSource *source,
- GrlMetadataSourceSetMetadataSpec *sms)
-{
- GRL_DEBUG ("grl_metadata_store_source_set_metadata");
-
- const gchar *media_id, *source_id;
- GError *error = NULL;
- GList *failed_keys = NULL;
-
- source_id = grl_media_get_source (sms->media);
- media_id = grl_media_get_id (sms->media);
-
- /* We need the source id */
- if (!source_id) {
- GRL_WARNING ("Failed to update metadata: source-id not available");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_SET_METADATA_FAILED,
- "source-id not available, cannot update metadata.");
- failed_keys = g_list_copy (sms->keys);
- } else {
- /* Special case for root categories */
- if (!media_id) {
- media_id = "";
- }
-
- failed_keys = write_keys (GRL_METADATA_STORE_SOURCE (source)->priv->db,
- source_id, media_id, sms, &error);
- }
-
- sms->callback (sms->source, sms->media, failed_keys, sms->user_data, error);
-
- if (error) {
- g_error_free (error);
- }
- g_list_free (failed_keys);
-}
diff --git a/src/metadata-store/grl-metadata-store.h b/src/metadata-store/grl-metadata-store.h
deleted file mode 100644
index 47df494..0000000
--- a/src/metadata-store/grl-metadata-store.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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_METADATA_STORE_SOURCE_H_
-#define _GRL_METADATA_STORE_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_METADATA_STORE_SOURCE_TYPE \
- (grl_metadata_store_source_get_type ())
-
-#define GRL_METADATA_STORE_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_METADATA_STORE_SOURCE_TYPE, \
- GrlMetadataStoreSource))
-
-#define GRL_IS_METADATA_STORE_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_METADATA_STORE_SOURCE_TYPE))
-
-#define GRL_METADATA_STORE_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_METADATA_STORE_SOURCE_TYPE, \
- GrlMetadataStoreSourceClass))
-
-#define GRL_IS_METADATA_STORE_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_METADATA_STORE_SOURCE_TYPE))
-
-#define GRL_METADATA_STORE_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_METADATA_STORE_SOURCE_TYPE, \
- GrlMetadataStoreSourceClass))
-
-typedef struct _GrlMetadataStorePrivate GrlMetadataStorePrivate;
-typedef struct _GrlMetadataStoreSource GrlMetadataStoreSource;
-
-struct _GrlMetadataStoreSource {
-
- GrlMetadataSource parent;
-
- /*< private >*/
- GrlMetadataStorePrivate *priv;
-};
-
-typedef struct _GrlMetadataStoreSourceClass GrlMetadataStoreSourceClass;
-
-struct _GrlMetadataStoreSourceClass {
-
- GrlMetadataSourceClass parent_class;
-
-};
-
-GType grl_metadata_store_source_get_type (void);
-
-#endif /* _GRL_METADATA_STORE_SOURCE_H_ */
diff --git a/src/metadata-store/grl-metadata-store.xml b/src/metadata-store/grl-metadata-store.xml
deleted file mode 100644
index b8bbbb8..0000000
--- a/src/metadata-store/grl-metadata-store.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Metadata Store</name>
- <description>A plugin for storing extra metadata information</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/metadata/fake-metadata/Makefile.am b/src/metadata/fake-metadata/Makefile.am
new file mode 100644
index 0000000..e0a1b81
--- /dev/null
+++ b/src/metadata/fake-metadata/Makefile.am
@@ -0,0 +1,32 @@
+#
+# Makefile.am
+#
+# Author: Iago Toral Quiroga <itoral igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlfakemetadata.la
+
+libgrlfakemetadata_la_CFLAGS = \
+ $(DEPS_CFLAGS)
+
+libgrlfakemetadata_la_LIBADD = \
+ $(DEPS_LIBS)
+
+libgrlfakemetadata_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlfakemetadata_la_SOURCES = grl-fake-metadata.c grl-fake-metadata.h
+
+libdir=$(GRL_PLUGINS_DIR)
+fakemetadataxmldir = $(GRL_PLUGINS_CONF_DIR)
+fakemetadataxml_DATA = $(FAKEMETADATA_PLUGIN_ID).xml
+
+EXTRA_DIST = $(fakemetadataxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/metadata/fake-metadata/grl-fake-metadata.c b/src/metadata/fake-metadata/grl-fake-metadata.c
new file mode 100644
index 0000000..463b284
--- /dev/null
+++ b/src/metadata/fake-metadata/grl-fake-metadata.c
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
+
+#include "grl-fake-metadata.h"
+
+#define GRL_LOG_DOMAIN_DEFAULT fake_metadata_log_domain
+GRL_LOG_DOMAIN_STATIC(fake_metadata_log_domain);
+
+#define PLUGIN_ID FAKEMETADATA_PLUGIN_ID
+
+#define SOURCE_ID "grl-fake-metadata"
+#define SOURCE_NAME "Fake Metadata Provider"
+#define SOURCE_DESC "A source for faking metadata resolution"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+
+static GrlFakeMetadataSource *grl_fake_metadata_source_new (void);
+
+static void grl_fake_metadata_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs);
+
+static void grl_fake_metadata_source_set_metadata (GrlMetadataSource *source,
+ GrlMetadataSourceSetMetadataSpec *sms);
+
+static const GList *grl_fake_metadata_source_supported_keys (GrlMetadataSource *source);
+
+static const GList *grl_fake_metadata_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id);
+
+static const GList *grl_fake_metadata_source_writable_keys (GrlMetadataSource *source);
+
+gboolean grl_fake_metadata_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+
+/* =================== GrlFakeMetadata Plugin =============== */
+
+gboolean
+grl_fake_metadata_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (fake_metadata_log_domain, "fake-metadata");
+
+ GRL_DEBUG ("grl_fake_metadata_source_plugin_init");
+
+ GrlFakeMetadataSource *source = grl_fake_metadata_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_fake_metadata_source_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== GrlFakeMetadata GObject ================ */
+
+static GrlFakeMetadataSource *
+grl_fake_metadata_source_new (void)
+{
+ GRL_DEBUG ("grl_fake_metadata_source_new");
+ return g_object_new (GRL_FAKE_METADATA_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_fake_metadata_source_class_init (GrlFakeMetadataSourceClass * klass)
+{
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ metadata_class->supported_keys = grl_fake_metadata_source_supported_keys;
+ metadata_class->key_depends = grl_fake_metadata_source_key_depends;
+ metadata_class->resolve = grl_fake_metadata_source_resolve;
+ metadata_class->set_metadata = grl_fake_metadata_source_set_metadata;
+ metadata_class->writable_keys = grl_fake_metadata_source_writable_keys;
+}
+
+static void
+grl_fake_metadata_source_init (GrlFakeMetadataSource *source)
+{
+}
+
+G_DEFINE_TYPE (GrlFakeMetadataSource,
+ grl_fake_metadata_source,
+ GRL_TYPE_METADATA_SOURCE);
+
+/* ======================= Utilities ==================== */
+
+static void
+fill_metadata (GrlMedia *media, GrlKeyID key_id)
+{
+ if (key_id == GRL_METADATA_KEY_AUTHOR) {
+ grl_media_set_author (media, "fake author");
+ } else if (key_id == GRL_METADATA_KEY_ARTIST) {
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_ARTIST, "fake artist");
+ } else if (key_id == GRL_METADATA_KEY_ALBUM) {
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_ALBUM, "fake album");
+ } else if (key_id == GRL_METADATA_KEY_GENRE) {
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_GENRE, "fake genre");
+ } else if (key_id == GRL_METADATA_KEY_DESCRIPTION) {
+ grl_media_set_description (media, "fake description");
+ } else if (key_id == GRL_METADATA_KEY_DURATION) {
+ grl_media_set_duration (media, 99);
+ } else if (key_id == GRL_METADATA_KEY_DATE) {
+ grl_data_set_string (GRL_DATA (media),
+ GRL_METADATA_KEY_DATE, "01/01/1970");
+ } else if (key_id == GRL_METADATA_KEY_THUMBNAIL) {
+ grl_media_set_thumbnail (media,
+ "http://fake.thumbnail.com/fake-image.jpg");
+ }
+}
+
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_fake_metadata_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_AUTHOR,
+ GRL_METADATA_KEY_ARTIST,
+ GRL_METADATA_KEY_ALBUM,
+ GRL_METADATA_KEY_GENRE,
+ GRL_METADATA_KEY_DESCRIPTION,
+ GRL_METADATA_KEY_DURATION,
+ GRL_METADATA_KEY_DATE,
+ GRL_METADATA_KEY_THUMBNAIL,
+ NULL);
+ }
+ return keys;
+}
+
+static const GList *
+grl_fake_metadata_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id)
+{
+ static GList *deps = NULL;
+ if (!deps) {
+ deps = grl_metadata_key_list_new (GRL_METADATA_KEY_TITLE, NULL);
+ }
+
+ if (key_id == GRL_METADATA_KEY_AUTHOR ||
+ key_id == GRL_METADATA_KEY_ARTIST ||
+ key_id == GRL_METADATA_KEY_ALBUM ||
+ key_id == GRL_METADATA_KEY_GENRE ||
+ key_id == GRL_METADATA_KEY_DESCRIPTION ||
+ key_id == GRL_METADATA_KEY_DURATION ||
+ key_id == GRL_METADATA_KEY_DATE ||
+ key_id == GRL_METADATA_KEY_THUMBNAIL) {
+ return deps;
+ }
+
+ return NULL;
+}
+
+static const GList *
+grl_fake_metadata_source_writable_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ALBUM,
+ GRL_METADATA_KEY_ARTIST,
+ GRL_METADATA_KEY_GENRE,
+ NULL);
+ }
+ return keys;
+}
+
+static void
+grl_fake_metadata_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs)
+{
+ GRL_DEBUG ("grl_fake_metadata_source_resolve");
+
+ GList *iter;
+
+ iter = rs->keys;
+ while (iter) {
+ fill_metadata (GRL_MEDIA (rs->media), iter->data);
+ iter = g_list_next (iter);
+ }
+
+ rs->callback (source, rs->media, rs->user_data, NULL);
+}
+
+static void
+grl_fake_metadata_source_set_metadata (GrlMetadataSource *source,
+ GrlMetadataSourceSetMetadataSpec *sms)
+{
+ GRL_DEBUG ("grl_fake_metadata_source_set_metadata");
+ GRL_DEBUG (" Faking set metadata for %d keys!", g_list_length (sms->keys));
+ sms->callback (sms->source, sms->media, NULL, sms->user_data, NULL);
+}
diff --git a/src/metadata/fake-metadata/grl-fake-metadata.h b/src/metadata/fake-metadata/grl-fake-metadata.h
new file mode 100644
index 0000000..5d80453
--- /dev/null
+++ b/src/metadata/fake-metadata/grl-fake-metadata.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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_FAKE_METADATA_SOURCE_H_
+#define _GRL_FAKE_METADATA_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_FAKE_METADATA_SOURCE_TYPE \
+ (grl_fake_metadata_source_get_type ())
+
+#define GRL_FAKE_METADATA_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_FAKE_METADATA_SOURCE_TYPE, \
+ GrlFakeMetadataSource))
+
+#define GRL_IS_FAKE_METADATA_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_FAKE_METADATA_SOURCE_TYPE))
+
+#define GRL_FAKE_METADATA_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_FAKE_METADATA_SOURCE_TYPE, \
+ GrlFakeMetadataSourceClass))
+
+#define GRL_IS_FAKE_METADATA_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_FAKE_METADATA_SOURCE_TYPE))
+
+#define GRL_FAKE_METADATA_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_FAKE_METADATA_SOURCE_TYPE, \
+ GrlFakeMetadataSourceClass))
+
+typedef struct _GrlFakeMetadataSource GrlFakeMetadataSource;
+
+struct _GrlFakeMetadataSource {
+
+ GrlMetadataSource parent;
+
+};
+
+typedef struct _GrlFakeMetadataSourceClass GrlFakeMetadataSourceClass;
+
+struct _GrlFakeMetadataSourceClass {
+
+ GrlMetadataSourceClass parent_class;
+
+};
+
+GType grl_fake_metadata_source_get_type (void);
+
+#endif /* _GRL_FAKE_METADATA_SOURCE_H_ */
diff --git a/src/metadata/fake-metadata/grl-fake-metadata.xml b/src/metadata/fake-metadata/grl-fake-metadata.xml
new file mode 100644
index 0000000..11e4d32
--- /dev/null
+++ b/src/metadata/fake-metadata/grl-fake-metadata.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Fake Metadata Provider</name>
+ <description>A plugin for faking metadata resolution</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/metadata/gravatar/Makefile.am b/src/metadata/gravatar/Makefile.am
new file mode 100644
index 0000000..391795e
--- /dev/null
+++ b/src/metadata/gravatar/Makefile.am
@@ -0,0 +1,32 @@
+#
+# Makefile.am
+#
+# Author: Juan A. Suarez Romero <jasuarez igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlgravatar.la
+
+libgrlgravatar_la_CFLAGS = \
+ $(DEPS_CFLAGS)
+
+libgrlgravatar_la_LIBADD = \
+ $(DEPS_LIBS)
+
+libgrlgravatar_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlgravatar_la_SOURCES = grl-gravatar.c grl-gravatar.h
+
+libdir=$(GRL_PLUGINS_DIR)
+gravatarxmldir = $(GRL_PLUGINS_CONF_DIR)
+gravatarxml_DATA = $(GRAVATAR_PLUGIN_ID).xml
+
+EXTRA_DIST = $(gravatarxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/metadata/gravatar/grl-gravatar.c b/src/metadata/gravatar/grl-gravatar.c
new file mode 100644
index 0000000..41d9fa5
--- /dev/null
+++ b/src/metadata/gravatar/grl-gravatar.c
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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 "grl-gravatar.h"
+
+/* ---------- Logging ---------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT gravatar_log_domain
+GRL_LOG_DOMAIN_STATIC(gravatar_log_domain);
+
+/* -------- Gravatar API -------- */
+
+#define GRAVATAR_URL "http://www.gravatar.com/avatar/%s.jpg"
+
+/* ------- Pluging Info -------- */
+
+#define PLUGIN_ID GRAVATAR_PLUGIN_ID
+
+#define SOURCE_ID PLUGIN_ID
+#define SOURCE_NAME "Avatar provider from Gravatar"
+#define SOURCE_DESC "A plugin to get avatars for artist and author fields"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+
+static GrlGravatarSource *grl_gravatar_source_new (void);
+
+static void grl_gravatar_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs);
+
+static const GList *grl_gravatar_source_supported_keys (GrlMetadataSource *source);
+
+static const GList *grl_gravatar_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id);
+
+static GrlKeyID register_gravatar_key (GrlPluginRegistry *registry,
+ const gchar *name,
+ const gchar *nick,
+ const gchar *blurb);
+
+gboolean grl_gravatar_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+GrlKeyID GRL_METADATA_KEY_ARTIST_AVATAR = NULL;
+GrlKeyID GRL_METADATA_KEY_AUTHOR_AVATAR = NULL;
+
+/* =================== Gravatar Plugin =============== */
+
+gboolean
+grl_gravatar_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (gravatar_log_domain, "gravatar");
+
+ GRL_DEBUG ("grl_gravatar_source_plugin_init");
+
+ /* Register keys */
+ GRL_METADATA_KEY_ARTIST_AVATAR =
+ register_gravatar_key (registry,
+ "artist-avatar",
+ "ArtistAvatar",
+ "Avatar for the artist");
+
+ GRL_METADATA_KEY_AUTHOR_AVATAR =
+ register_gravatar_key (registry,
+ "author-avatar",
+ "AuthorAvatar",
+ "Avatar for the author");
+ if (!GRL_METADATA_KEY_ARTIST_AVATAR &&
+ !GRL_METADATA_KEY_AUTHOR_AVATAR) {
+ GRL_WARNING ("Unable to register \"autor-avatar\" nor \"artist-avatar\"");
+ return FALSE;
+ }
+
+ GrlGravatarSource *source = grl_gravatar_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_gravatar_source_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Gravatar GObject ================ */
+
+static GrlGravatarSource *
+grl_gravatar_source_new (void)
+{
+ GRL_DEBUG ("grl_gravatar_source_new");
+ return g_object_new (GRL_GRAVATAR_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_gravatar_source_class_init (GrlGravatarSourceClass * klass)
+{
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ metadata_class->supported_keys = grl_gravatar_source_supported_keys;
+ metadata_class->key_depends = grl_gravatar_source_key_depends;
+ metadata_class->resolve = grl_gravatar_source_resolve;
+}
+
+static void
+grl_gravatar_source_init (GrlGravatarSource *source)
+{
+}
+
+G_DEFINE_TYPE (GrlGravatarSource,
+ grl_gravatar_source,
+ GRL_TYPE_METADATA_SOURCE);
+
+/* ======================= Utilities ==================== */
+
+static GrlKeyID
+register_gravatar_key (GrlPluginRegistry *registry,
+ const gchar *name,
+ const gchar *nick,
+ const gchar *blurb)
+{
+ GParamSpec *spec;
+ GrlKeyID key;
+
+ spec = g_param_spec_string (name,
+ nick,
+ blurb,
+ NULL,
+ G_PARAM_READWRITE);
+
+ key = grl_plugin_registry_register_metadata_key (registry, spec, NULL);
+
+ /* If key was not registered, could be that it is already registered. If so,
+ check if type is the expected one, and reuse it */
+ if (!key) {
+ g_param_spec_unref (spec);
+ key = grl_plugin_registry_lookup_metadata_key (registry, name);
+ if (!key || GRL_METADATA_KEY_GET_TYPE (key) != G_TYPE_STRING) {
+ key = NULL;
+ }
+ }
+
+ return key;
+}
+
+static gchar *
+get_avatar (const gchar *field) {
+ GMatchInfo *match_info = NULL;
+ gchar *avatar = NULL;
+ gchar *email;
+ gchar *email_hash;
+ gchar *lowercased_field;
+ static GRegex *email_regex = NULL;
+
+ if (!field) {
+ return NULL;
+ }
+
+ lowercased_field = g_utf8_strdown (field, -1);
+
+ if (!email_regex) {
+ email_regex = g_regex_new ("[\\w-]+@([\\w-]+\\.)+[\\w-]+", G_REGEX_OPTIMIZE, 0, NULL);
+ }
+
+ if (g_regex_match (email_regex, lowercased_field, 0, &match_info)) {
+ email = g_match_info_fetch (match_info, 0);
+ g_match_info_free (match_info);
+ email_hash = g_compute_checksum_for_string (G_CHECKSUM_MD5, email, -1);
+ avatar = g_strdup_printf (GRAVATAR_URL, email_hash);
+ g_free (email);
+ g_free (email_hash);
+ }
+
+ return avatar;
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_gravatar_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+
+ if (!keys) {
+ if (GRL_METADATA_KEY_ARTIST_AVATAR) {
+ keys = g_list_prepend (keys, GRL_METADATA_KEY_ARTIST_AVATAR);
+ }
+ if (GRL_METADATA_KEY_AUTHOR_AVATAR) {
+ keys =g_list_prepend (keys, GRL_METADATA_KEY_AUTHOR_AVATAR);
+ }
+ }
+
+ return keys;
+}
+
+static const GList *
+grl_gravatar_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id)
+{
+ static GList *artist_avatar_deps = NULL;
+ static GList *author_avatar_deps = NULL;
+
+ if (!artist_avatar_deps) {
+ artist_avatar_deps = grl_metadata_key_list_new (GRL_METADATA_KEY_ARTIST,
+ NULL);
+ }
+
+ if (!author_avatar_deps) {
+ author_avatar_deps = grl_metadata_key_list_new (GRL_METADATA_KEY_AUTHOR,
+ NULL);
+ }
+
+ if (key_id == GRL_METADATA_KEY_ARTIST_AVATAR) {
+ return artist_avatar_deps;
+ } else if (key_id == GRL_METADATA_KEY_AUTHOR_AVATAR) {
+ return author_avatar_deps;
+ } else {
+ return NULL;
+ }
+}
+
+static void
+grl_gravatar_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs)
+{
+ gboolean artist_avatar_required = FALSE;
+ gboolean author_avatar_required = FALSE;
+ gchar *avatar_url;
+
+ GRL_DEBUG ("grl_gravatar_source_resolve");
+
+ GList *iter;
+
+ /* Check that albumart is requested */
+ iter = rs->keys;
+ while (iter && (!artist_avatar_required || !author_avatar_required)) {
+ if (iter->data == GRL_METADATA_KEY_ARTIST_AVATAR) {
+ artist_avatar_required = TRUE;
+ } else if (iter->data == GRL_METADATA_KEY_AUTHOR_AVATAR) {
+ author_avatar_required = TRUE;
+ }
+ iter = g_list_next (iter);
+ }
+
+ if (artist_avatar_required) {
+ avatar_url = get_avatar (grl_data_get_string (GRL_DATA (rs->media),
+ GRL_METADATA_KEY_ARTIST));
+ if (avatar_url) {
+ grl_data_set_string (GRL_DATA (rs->media),
+ GRL_METADATA_KEY_ARTIST_AVATAR,
+ avatar_url);
+ g_free (avatar_url);
+ }
+ }
+
+ if (author_avatar_required) {
+ avatar_url = get_avatar (grl_data_get_string (GRL_DATA (rs->media),
+ GRL_METADATA_KEY_AUTHOR));
+ if (avatar_url) {
+ grl_data_set_string (GRL_DATA (rs->media),
+ GRL_METADATA_KEY_AUTHOR_AVATAR,
+ avatar_url);
+ g_free (avatar_url);
+ }
+ }
+
+ rs->callback (source, rs->media, rs->user_data, NULL);
+}
diff --git a/src/metadata/gravatar/grl-gravatar.h b/src/metadata/gravatar/grl-gravatar.h
new file mode 100644
index 0000000..644efdb
--- /dev/null
+++ b/src/metadata/gravatar/grl-gravatar.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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_GRAVATAR_H_
+#define _GRL_GRAVATAR_H_
+
+#include <grilo.h>
+
+#define GRL_GRAVATAR_SOURCE_TYPE \
+ (grl_gravatar_source_get_type ())
+
+#define GRL_GRAVATAR_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_GRAVATAR_SOURCE_TYPE, \
+ GrlGravatarSource))
+
+#define GRL_IS_GRAVATAR_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_GRAVATAR_SOURCE_TYPE))
+
+#define GRL_GRAVATAR_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_GRAVATAR_SOURCE_TYPE, \
+ GrlGravatarSourceClass))
+
+#define GRL_IS_GRAVATAR_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_GRAVATAR_SOURCE_TYPE))
+
+#define GRL_GRAVATAR_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_GRAVATAR_SOURCE_TYPE, \
+ GrlGravatarSourceClass))
+
+typedef struct _GrlGravatarSource GrlGravatarSource;
+
+struct _GrlGravatarSource {
+
+ GrlMetadataSource parent;
+
+};
+
+typedef struct _GrlGravatarSourceClass GrlGravatarSourceClass;
+
+struct _GrlGravatarSourceClass {
+
+ GrlMetadataSourceClass parent_class;
+
+};
+
+GType grl_gravatar_source_get_type (void);
+
+#endif /* _GRL_GRAVATAR_H_ */
diff --git a/src/metadata/gravatar/grl-gravatar.xml b/src/metadata/gravatar/grl-gravatar.xml
new file mode 100644
index 0000000..58f60d1
--- /dev/null
+++ b/src/metadata/gravatar/grl-gravatar.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Avatar provider from Gravatar</name>
+ <description>A plugin to get avatars for artist and author fields</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/metadata/lastfm-albumart/Makefile.am b/src/metadata/lastfm-albumart/Makefile.am
new file mode 100644
index 0000000..ad7bc6e
--- /dev/null
+++ b/src/metadata/lastfm-albumart/Makefile.am
@@ -0,0 +1,36 @@
+#
+# Makefile.am
+#
+# Author: Juan A. Suarez Romero <jasuarez igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrllastfm-albumart.la
+
+libgrllastfm_albumart_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GRLNET_CFLAGS) \
+ $(XML_CFLAGS)
+
+libgrllastfm_albumart_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GRLNET_LIBS) \
+ $(XML_LIBS)
+
+libgrllastfm_albumart_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrllastfm_albumart_la_SOURCES = grl-lastfm-albumart.c grl-lastfm-albumart.h
+
+libdir = $(GRL_PLUGINS_DIR)
+lastfmalbumartxmldir = $(GRL_PLUGINS_CONF_DIR)
+lastfmalbumartxml_DATA = $(LASTFM_ALBUMART_PLUGIN_ID).xml
+
+EXTRA_DIST = $(lastfmalbumartxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/metadata/lastfm-albumart/grl-lastfm-albumart.c b/src/metadata/lastfm-albumart/grl-lastfm-albumart.c
new file mode 100644
index 0000000..99825a2
--- /dev/null
+++ b/src/metadata/lastfm-albumart/grl-lastfm-albumart.c
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <net/grl-net.h>
+#include <libxml/parser.h>
+#include <libxml/xmlmemory.h>
+#include <libxml/xpath.h>
+
+#include "grl-lastfm-albumart.h"
+
+/* ---------- Logging ---------- */
+
+#define GRL_LOG_DOMAIN_DEFAULT lastfm_albumart_log_domain
+GRL_LOG_DOMAIN_STATIC(lastfm_albumart_log_domain);
+
+/* -------- Last.FM API -------- */
+
+#define LASTFM_GET_ALBUM "http://ws.audioscrobbler.com/1.0/album/%s/%s/info.xml"
+#define LASTFM_XML_COVER "/album/coverart/medium"
+
+/* ------- Pluging Info -------- */
+
+#define PLUGIN_ID LASTFM_ALBUMART_PLUGIN_ID
+
+#define SOURCE_ID "grl-lastfm-albumart"
+#define SOURCE_NAME "Album art Provider from Last.FM"
+#define SOURCE_DESC "A plugin for getting album arts using Last.FM as backend"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+static GrlNetWc *wc;
+
+static GrlLastfmAlbumartSource *grl_lastfm_albumart_source_new (void);
+
+static void grl_lastfm_albumart_source_finalize (GObject *object);
+
+static void grl_lastfm_albumart_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs);
+
+static const GList *grl_lastfm_albumart_source_supported_keys (GrlMetadataSource *source);
+
+static const GList *grl_lastfm_albumart_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id);
+
+gboolean grl_lastfm_albumart_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+
+/* =================== Last.FM-AlbumArt Plugin =============== */
+
+gboolean
+grl_lastfm_albumart_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (lastfm_albumart_log_domain, "lastfm-albumart");
+
+ GRL_DEBUG ("grl_lastfm_albumart_source_plugin_init");
+
+ GrlLastfmAlbumartSource *source = grl_lastfm_albumart_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_lastfm_albumart_source_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== Last.FM-AlbumArt GObject ================ */
+
+static GrlLastfmAlbumartSource *
+grl_lastfm_albumart_source_new (void)
+{
+ GRL_DEBUG ("grl_lastfm_albumart_source_new");
+ return g_object_new (GRL_LASTFM_ALBUMART_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_lastfm_albumart_source_class_init (GrlLastfmAlbumartSourceClass * klass)
+{
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ metadata_class->supported_keys = grl_lastfm_albumart_source_supported_keys;
+ metadata_class->key_depends = grl_lastfm_albumart_source_key_depends;
+ metadata_class->resolve = grl_lastfm_albumart_source_resolve;
+
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ gobject_class->finalize = grl_lastfm_albumart_source_finalize;
+}
+
+static void
+grl_lastfm_albumart_source_init (GrlLastfmAlbumartSource *source)
+{
+}
+
+G_DEFINE_TYPE (GrlLastfmAlbumartSource,
+ grl_lastfm_albumart_source,
+ GRL_TYPE_METADATA_SOURCE);
+
+static void
+grl_lastfm_albumart_source_finalize (GObject *object)
+{
+ if (wc && GRL_IS_NET_WC (wc))
+ g_object_unref (wc);
+
+ G_OBJECT_CLASS (grl_lastfm_albumart_source_parent_class)->finalize (object);
+}
+
+/* ======================= Utilities ==================== */
+
+static gchar *
+xml_get_image (const gchar *xmldata)
+{
+ xmlDocPtr doc;
+ xmlXPathContextPtr xpath_ctx;
+ xmlXPathObjectPtr xpath_res;
+ gchar *image = NULL;
+
+ doc = xmlReadMemory (xmldata, xmlStrlen ((xmlChar*) xmldata), NULL, NULL,
+ XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
+ if (!doc) {
+ return NULL;
+ }
+
+ xpath_ctx = xmlXPathNewContext (doc);
+ if (!xpath_ctx) {
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ xpath_res = xmlXPathEvalExpression ((xmlChar *) LASTFM_XML_COVER,
+ xpath_ctx);
+ if (!xpath_res) {
+ xmlXPathFreeContext (xpath_ctx);
+ xmlFreeDoc (doc);
+ return NULL;
+ }
+
+ if (xpath_res->nodesetval->nodeTab) {
+ image =
+ (gchar *) xmlNodeListGetString (doc,
+ xpath_res->nodesetval->nodeTab[0]->xmlChildrenNode,
+ 1);
+ }
+ xmlXPathFreeObject (xpath_res);
+ xmlXPathFreeContext (xpath_ctx);
+ xmlFreeDoc (doc);
+
+ return image;
+}
+
+static void
+read_done_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ GrlMetadataSourceResolveSpec *rs =
+ (GrlMetadataSourceResolveSpec *) user_data;
+ GError *error = NULL;
+ GError *wc_error = NULL;
+ gchar *content = NULL;
+ gchar *image = NULL;
+
+ if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
+ res,
+ &content,
+ NULL,
+ &wc_error)) {
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_RESOLVE_FAILED,
+ "Failed to connect to Last.FM: '%s'",
+ wc_error->message);
+ rs->callback (rs->source, rs->media, rs->user_data, error);
+ g_error_free (wc_error);
+ g_error_free (error);
+
+ return;
+ }
+
+ image = xml_get_image (content);
+ if (image) {
+ grl_data_set_string (GRL_DATA (rs->media),
+ GRL_METADATA_KEY_THUMBNAIL,
+ image);
+ g_free (image);
+ }
+
+ rs->callback (rs->source, rs->media, rs->user_data, NULL);
+}
+
+static void
+read_url_async (const gchar *url, gpointer user_data)
+{
+ if (!wc)
+ wc = grl_net_wc_new ();
+
+ GRL_DEBUG ("Opening '%s'", url);
+ grl_net_wc_request_async (wc, url, NULL, read_done_cb, user_data);
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_lastfm_albumart_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_THUMBNAIL,
+ NULL);
+ }
+
+ return keys;
+}
+
+static const GList *
+grl_lastfm_albumart_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id)
+{
+ static GList *deps = NULL;
+
+ if (!deps) {
+ deps = grl_metadata_key_list_new (GRL_METADATA_KEY_ARTIST,
+ GRL_METADATA_KEY_ALBUM,
+ NULL);
+ }
+
+ if (key_id == GRL_METADATA_KEY_THUMBNAIL) {
+ return deps;
+ }
+
+ return NULL;
+}
+
+static void
+grl_lastfm_albumart_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs)
+{
+ const gchar *artist = NULL;
+ const gchar *album = NULL;
+ gchar *esc_artist = NULL;
+ gchar *esc_album = NULL;
+ gchar *url = NULL;
+
+ GRL_DEBUG ("grl_lastfm_albumart_source_resolve");
+
+ GList *iter;
+
+ /* Check that albumart is requested */
+ iter = rs->keys;
+ while (iter) {
+ if (iter->data == GRL_METADATA_KEY_THUMBNAIL) {
+ break;
+ } else {
+ iter = g_list_next (iter);
+ }
+ }
+
+ if (iter == NULL) {
+ GRL_DEBUG ("No supported key was requested");
+ rs->callback (source, rs->media, rs->user_data, NULL);
+ } else {
+ artist = grl_data_get_string (GRL_DATA (rs->media),
+ GRL_METADATA_KEY_ARTIST);
+
+ album = grl_data_get_string (GRL_DATA (rs->media),
+ GRL_METADATA_KEY_ALBUM);
+
+ if (!artist || !album) {
+ GRL_DEBUG ("Missing dependencies");
+ rs->callback (source, rs->media, rs->user_data, NULL);
+ } else {
+ esc_artist = g_uri_escape_string (artist, NULL, TRUE);
+ esc_album = g_uri_escape_string (album, NULL, TRUE);
+ url = g_strdup_printf (LASTFM_GET_ALBUM, esc_artist, esc_album);
+ read_url_async (url, rs);
+ g_free (esc_artist);
+ g_free (esc_album);
+ g_free (url);
+ }
+ }
+}
diff --git a/src/metadata/lastfm-albumart/grl-lastfm-albumart.h b/src/metadata/lastfm-albumart/grl-lastfm-albumart.h
new file mode 100644
index 0000000..43a5ab5
--- /dev/null
+++ b/src/metadata/lastfm-albumart/grl-lastfm-albumart.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia com>
+ *
+ * Authors: Juan A. Suarez Romero <jasuarez igalia 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_LASTFM_ALBUMART_SOURCE_H_
+#define _GRL_LASTFM_ALBUMART_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_LASTFM_ALBUMART_SOURCE_TYPE \
+ (grl_lastfm_albumart_source_get_type ())
+
+#define GRL_LASTFM_ALBUMART_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_LASTFM_ALBUMART_SOURCE_TYPE, \
+ GrlLastfmAlbumartSource))
+
+#define GRL_IS_LASTFM_ALBUMART_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_LASTFM_ALBUMART_SOURCE_TYPE))
+
+#define GRL_LASTFM_ALBUMART_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_LASTFM_ALBUMART_SOURCE_TYPE, \
+ GrlLastfmAlbumartSourceClass))
+
+#define GRL_IS_LASTFM_ALBUMART_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_LASTFM_ALBUMART_SOURCE_TYPE))
+
+#define GRL_LASTFM_ALBUMART_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_LASTFM_ALBUMART_SOURCE_TYPE, \
+ GrlLastfmAlbumartSourceClass))
+
+typedef struct _GrlLastfmAlbumartSource GrlLastfmAlbumartSource;
+
+struct _GrlLastfmAlbumartSource {
+
+ GrlMetadataSource parent;
+
+};
+
+typedef struct _GrlLastfmAlbumartSourceClass GrlLastfmAlbumartSourceClass;
+
+struct _GrlLastfmAlbumartSourceClass {
+
+ GrlMetadataSourceClass parent_class;
+
+};
+
+GType grl_lastfm_albumart_source_get_type (void);
+
+#endif /* _GRL_LASTFM_ALBUMART_SOURCE_H_ */
diff --git a/src/metadata/lastfm-albumart/grl-lastfm-albumart.xml b/src/metadata/lastfm-albumart/grl-lastfm-albumart.xml
new file mode 100644
index 0000000..f2b64dd
--- /dev/null
+++ b/src/metadata/lastfm-albumart/grl-lastfm-albumart.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Album art Provider from Last.FM</name>
+ <description>A plugin for getting album arts using Last.FM as backend</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/metadata/local-metadata/Makefile.am b/src/metadata/local-metadata/Makefile.am
new file mode 100644
index 0000000..6840cf3
--- /dev/null
+++ b/src/metadata/local-metadata/Makefile.am
@@ -0,0 +1,34 @@
+#
+# Makefile.am
+#
+# Author: Guillaume Emont <gemont igalia com>
+#
+# Copyright (C) 2010-2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrllocalmetadata.la
+
+libgrllocalmetadata_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(GIO_CFLAGS)
+
+libgrllocalmetadata_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(GIO_LIBS)
+
+libgrllocalmetadata_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrllocalmetadata_la_SOURCES = grl-local-metadata.c grl-local-metadata.h
+
+libdir=$(GRL_PLUGINS_DIR)
+localmetadataxmldir = $(GRL_PLUGINS_CONF_DIR)
+localmetadataxml_DATA = $(LOCALMETADATA_PLUGIN_ID).xml
+
+EXTRA_DIST = $(localmetadataxml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/metadata/local-metadata/grl-local-metadata.c b/src/metadata/local-metadata/grl-local-metadata.c
new file mode 100644
index 0000000..a535f42
--- /dev/null
+++ b/src/metadata/local-metadata/grl-local-metadata.c
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2010-2011 Igalia S.L.
+ *
+ * Contact: Guillaume Emont <gemont igalia 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 <grilo.h>
+#include <gio/gio.h>
+
+#include "grl-local-metadata.h"
+
+#define GRL_LOG_DOMAIN_DEFAULT local_metadata_log_domain
+GRL_LOG_DOMAIN_STATIC(local_metadata_log_domain);
+
+#define PLUGIN_ID LOCALMETADATA_PLUGIN_ID
+
+#define SOURCE_ID "grl-local-metadata"
+#define SOURCE_NAME "Local Metadata Provider"
+#define SOURCE_DESC "A source providing locally available metadata"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+
+static GrlLocalMetadataSource *grl_local_metadata_source_new (void);
+
+static void grl_local_metadata_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs);
+
+static const GList *grl_local_metadata_source_supported_keys (GrlMetadataSource *source);
+
+static const GList * grl_local_metadata_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id);
+
+gboolean grl_local_metadata_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+
+/* =================== GrlLocalMetadata Plugin =============== */
+
+gboolean
+grl_local_metadata_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (local_metadata_log_domain, "local-metadata");
+
+ GRL_DEBUG ("grl_local_metadata_source_plugin_init");
+
+ GrlLocalMetadataSource *source = grl_local_metadata_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_local_metadata_source_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== GrlLocalMetadata GObject ================ */
+
+static GrlLocalMetadataSource *
+grl_local_metadata_source_new (void)
+{
+ GRL_DEBUG ("grl_local_metadata_source_new");
+ return g_object_new (GRL_LOCAL_METADATA_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_local_metadata_source_class_init (GrlLocalMetadataSourceClass * klass)
+{
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ metadata_class->supported_keys = grl_local_metadata_source_supported_keys;
+ metadata_class->key_depends = grl_local_metadata_source_key_depends;
+ metadata_class->resolve = grl_local_metadata_source_resolve;
+}
+
+static void
+grl_local_metadata_source_init (GrlLocalMetadataSource *source)
+{
+}
+
+G_DEFINE_TYPE (GrlLocalMetadataSource,
+ grl_local_metadata_source,
+ GRL_TYPE_METADATA_SOURCE);
+
+/* ======================= Utilities ==================== */
+static void
+got_file_info (GFile *file, GAsyncResult *result,
+ GrlMetadataSourceResolveSpec *rs)
+{
+ GFileInfo *info;
+ GError *error = NULL;
+ const gchar *thumbnail_path;
+
+ GRL_DEBUG ("got_file_info");
+
+ info = g_file_query_info_finish (file, result, &error);
+ if (error)
+ goto error;
+
+ thumbnail_path =
+ g_file_info_get_attribute_byte_string (info, G_FILE_ATTRIBUTE_THUMBNAIL_PATH);
+
+
+ if (thumbnail_path) {
+ gchar *thumbnail_uri = g_filename_to_uri (thumbnail_path, NULL, &error);
+ if (error)
+ goto error;
+
+ GRL_INFO ("Got thumbnail %s for media: %s", thumbnail_uri,
+ grl_media_get_url (rs->media));
+ grl_media_set_thumbnail (rs->media, thumbnail_uri);
+ g_free (thumbnail_uri);
+
+ rs->callback (rs->source, rs->media, rs->user_data, NULL);
+ } else {
+ GRL_INFO ("Could not find thumbnail for media: %s",
+ grl_media_get_url (rs->media));
+ rs->callback (rs->source, rs->media, rs->user_data, NULL);
+ }
+
+ goto exit;
+
+error:
+ {
+ GError *new_error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
+ "Got error: %s", error->message);
+ rs->callback (rs->source, rs->media, rs->user_data, new_error);
+
+ g_error_free (error);
+ g_error_free (new_error);
+ }
+
+exit:
+ if (info)
+ g_object_unref (info);
+}
+
+static void
+resolve_image (GrlMetadataSourceResolveSpec *rs)
+{
+ GFile *file;
+
+ GRL_DEBUG ("resolve_image");
+
+ file = g_file_new_for_uri (grl_media_get_url (rs->media));
+
+ g_file_query_info_async (file, G_FILE_ATTRIBUTE_THUMBNAIL_PATH,
+ G_FILE_QUERY_INFO_NONE, G_PRIORITY_DEFAULT, NULL,
+ (GAsyncReadyCallback)got_file_info, rs);
+}
+
+static void
+resolve_album_art (GrlMetadataSourceResolveSpec *rs)
+{
+ /* FIXME: implement this, according to
+ * http://live.gnome.org/MediaArtStorageSpec */
+ GError *error;
+ error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
+ "Thumbnail resolution for GrlMediaAudio not implemented in local-metadata");
+ rs->callback (rs->source, rs->media, rs->user_data, error);
+ g_error_free (error);
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_local_metadata_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_THUMBNAIL,
+ NULL);
+ }
+ return keys;
+}
+
+static const GList *
+grl_local_metadata_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id)
+{
+ static GList *deps = NULL;
+ if (!deps) {
+ deps = grl_metadata_key_list_new (GRL_METADATA_KEY_URL, NULL);
+ }
+
+ if (key_id == GRL_METADATA_KEY_THUMBNAIL)
+ return deps;
+
+ return NULL;
+}
+
+static void
+grl_local_metadata_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs)
+{
+ const gchar *url;
+ gchar *scheme;
+ GError *error = NULL;
+
+ GRL_DEBUG ("grl_local_metadata_source_resolve");
+
+ url = grl_media_get_url (rs->media);
+ scheme = g_uri_parse_scheme (url);
+
+ if (0 != g_strcmp0 (scheme, "file"))
+ error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
+ "local-metadata needs a url in the file:// scheme");
+ else if (!g_list_find (rs->keys, GRL_METADATA_KEY_THUMBNAIL))
+ error = g_error_new (GRL_CORE_ERROR, GRL_CORE_ERROR_RESOLVE_FAILED,
+ "local-metadata can only resolve the thumbnail key");
+
+ if (error) {
+ /* No can do! */
+ rs->callback (source, rs->media, rs->user_data, error);
+ g_error_free (error);
+ goto exit;
+ }
+
+ if (GRL_IS_MEDIA_VIDEO (rs->media)
+ || GRL_IS_MEDIA_IMAGE (rs->media)) {
+ resolve_image (rs);
+ } else if (GRL_IS_MEDIA_AUDIO (rs->media)) {
+ resolve_album_art (rs);
+ } else {
+ /* What's that media type? */
+ rs->callback (source, rs->media, rs->user_data, NULL);
+ }
+
+exit:
+ g_free (scheme);
+}
+
diff --git a/src/metadata/local-metadata/grl-local-metadata.h b/src/metadata/local-metadata/grl-local-metadata.h
new file mode 100644
index 0000000..448051e
--- /dev/null
+++ b/src/metadata/local-metadata/grl-local-metadata.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2010-2011 Igalia S.L.
+ *
+ * Contact: Guillaume Emont <gemont igalia 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_LOCAL_METADATA_SOURCE_H_
+#define _GRL_LOCAL_METADATA_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_LOCAL_METADATA_SOURCE_TYPE \
+ (grl_local_metadata_source_get_type ())
+
+#define GRL_LOCAL_METADATA_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_LOCAL_METADATA_SOURCE_TYPE, \
+ GrlLocalMetadataSource))
+
+#define GRL_IS_LOCAL_METADATA_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_LOCAL_METADATA_SOURCE_TYPE))
+
+#define GRL_LOCAL_METADATA_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_LOCAL_METADATA_SOURCE_TYPE, \
+ GrlLocalMetadataSourceClass))
+
+#define GRL_IS_LOCAL_METADATA_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_LOCAL_METADATA_SOURCE_TYPE))
+
+#define GRL_LOCAL_METADATA_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_LOCAL_METADATA_SOURCE_TYPE, \
+ GrlLocalMetadataSourceClass))
+
+typedef struct _GrlLocalMetadataSource GrlLocalMetadataSource;
+
+struct _GrlLocalMetadataSource {
+
+ GrlMetadataSource parent;
+
+};
+
+typedef struct _GrlLocalMetadataSourceClass GrlLocalMetadataSourceClass;
+
+struct _GrlLocalMetadataSourceClass {
+
+ GrlMetadataSourceClass parent_class;
+
+};
+
+GType grl_local_metadata_source_get_type (void);
+
+#endif /* _GRL_LOCAL_METADATA_SOURCE_H_ */
diff --git a/src/metadata/local-metadata/grl-local-metadata.xml b/src/metadata/local-metadata/grl-local-metadata.xml
new file mode 100644
index 0000000..073392e
--- /dev/null
+++ b/src/metadata/local-metadata/grl-local-metadata.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Local Metadata Provider</name>
+ <description>A plugin that gets simple-to-obtain metadata from the local system</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/metadata/metadata-store/Makefile.am b/src/metadata/metadata-store/Makefile.am
new file mode 100644
index 0000000..372f4a8
--- /dev/null
+++ b/src/metadata/metadata-store/Makefile.am
@@ -0,0 +1,34 @@
+#
+# Makefile.am
+#
+# Author: Iago Toral Quiroga <itoral igalia com>
+#
+# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
+
+lib_LTLIBRARIES = libgrlmetadatastore.la
+
+libgrlmetadatastore_la_CFLAGS = \
+ $(DEPS_CFLAGS) \
+ $(SQLITE_CFLAGS)
+
+libgrlmetadatastore_la_LIBADD = \
+ $(DEPS_LIBS) \
+ $(SQLITE_LIBS)
+
+libgrlmetadatastore_la_LDFLAGS = \
+ -module \
+ -avoid-version
+
+libgrlmetadatastore_la_SOURCES = grl-metadata-store.c grl-metadata-store.h
+
+libdir=$(GRL_PLUGINS_DIR)
+metadatastorexmldir = $(GRL_PLUGINS_CONF_DIR)
+metadatastorexml_DATA = $(METADATA_STORE_PLUGIN_ID).xml
+
+EXTRA_DIST = $(metadatastorexml_DATA)
+
+MAINTAINERCLEANFILES = \
+ *.in \
+ *~
+
+DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/metadata/metadata-store/grl-metadata-store.c b/src/metadata/metadata-store/grl-metadata-store.c
new file mode 100644
index 0000000..f596585
--- /dev/null
+++ b/src/metadata/metadata-store/grl-metadata-store.c
@@ -0,0 +1,650 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
+#include <sqlite3.h>
+#include <string.h>
+
+#include "grl-metadata-store.h"
+
+#define GRL_METADATA_STORE_GET_PRIVATE(object) \
+ (G_TYPE_INSTANCE_GET_PRIVATE((object), \
+ GRL_METADATA_STORE_SOURCE_TYPE, \
+ GrlMetadataStorePrivate))
+
+#define GRL_LOG_DOMAIN_DEFAULT metadata_store_log_domain
+GRL_LOG_DOMAIN_STATIC(metadata_store_log_domain);
+
+#define PLUGIN_ID METADATA_STORE_PLUGIN_ID
+
+#define SOURCE_ID "grl-metadata-store"
+#define SOURCE_NAME "Metadata Store"
+#define SOURCE_DESC "A plugin for storing extra metadata information"
+
+#define AUTHOR "Igalia S.L."
+#define LICENSE "LGPL"
+#define SITE "http://www.igalia.com"
+
+#define GRL_SQL_DB ".grl-metadata-store"
+
+#define GRL_SQL_CREATE_TABLE_STORE \
+ "CREATE TABLE IF NOT EXISTS store (" \
+ "source_id TEXT," \
+ "media_id TEXT," \
+ "play_count INTEGER," \
+ "rating REAL," \
+ "last_position INTEGER," \
+ "last_played DATE)"
+
+#define GRL_SQL_GET_METADATA \
+ "SELECT * FROM store " \
+ "WHERE source_id='%s' AND media_id='%s' " \
+ "LIMIT 1"
+
+#define GRL_SQL_UPDATE_METADATA \
+ "UPDATE store SET %s " \
+ "WHERE source_id=? AND media_id=?"
+
+#define GRL_SQL_INSERT_METADATA \
+ "INSERT INTO store " \
+ "(%s source_id, media_id) VALUES " \
+ "(%s ?, ?)"
+
+struct _GrlMetadataStorePrivate {
+ sqlite3 *db;
+};
+
+enum {
+ STORE_SOURCE_ID = 0,
+ STORE_MEDIA_ID,
+ STORE_PLAY_COUNT,
+ STORE_RATING,
+ STORE_LAST_POSITION,
+ STORE_LAST_PLAYED,
+};
+
+static GrlMetadataStoreSource *grl_metadata_store_source_new (void);
+
+static void grl_metadata_store_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs);
+
+static void grl_metadata_store_source_set_metadata (GrlMetadataSource *source,
+ GrlMetadataSourceSetMetadataSpec *sms);
+
+static const GList *grl_metadata_store_source_supported_keys (GrlMetadataSource *source);
+
+static const GList *grl_metadata_store_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id);
+static const GList *grl_metadata_store_source_writable_keys (GrlMetadataSource *source);
+
+gboolean grl_metadata_store_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs);
+
+
+/* =================== GrlMetadataStore Plugin =============== */
+
+gboolean
+grl_metadata_store_source_plugin_init (GrlPluginRegistry *registry,
+ const GrlPluginInfo *plugin,
+ GList *configs)
+{
+ GRL_LOG_DOMAIN_INIT (metadata_store_log_domain, "metadata-store");
+
+ GRL_DEBUG ("grl_metadata_store_source_plugin_init");
+
+ GrlMetadataStoreSource *source = grl_metadata_store_source_new ();
+ grl_plugin_registry_register_source (registry,
+ plugin,
+ GRL_MEDIA_PLUGIN (source),
+ NULL);
+ return TRUE;
+}
+
+GRL_PLUGIN_REGISTER (grl_metadata_store_source_plugin_init,
+ NULL,
+ PLUGIN_ID);
+
+/* ================== GrlMetadataStore GObject ================ */
+
+static GrlMetadataStoreSource *
+grl_metadata_store_source_new (void)
+{
+ GRL_DEBUG ("grl_metadata_store_source_new");
+ return g_object_new (GRL_METADATA_STORE_SOURCE_TYPE,
+ "source-id", SOURCE_ID,
+ "source-name", SOURCE_NAME,
+ "source-desc", SOURCE_DESC,
+ NULL);
+}
+
+static void
+grl_metadata_store_source_class_init (GrlMetadataStoreSourceClass * klass)
+{
+ GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
+ metadata_class->supported_keys = grl_metadata_store_source_supported_keys;
+ metadata_class->key_depends = grl_metadata_store_source_key_depends;
+ metadata_class->writable_keys = grl_metadata_store_source_writable_keys;
+ metadata_class->resolve = grl_metadata_store_source_resolve;
+ metadata_class->set_metadata = grl_metadata_store_source_set_metadata;
+
+ g_type_class_add_private (klass, sizeof (GrlMetadataStorePrivate));
+}
+
+static void
+grl_metadata_store_source_init (GrlMetadataStoreSource *source)
+{
+ gint r;
+ const gchar *home;
+ gchar *db_path;
+ gchar *sql_error = NULL;
+
+ source->priv = GRL_METADATA_STORE_GET_PRIVATE (source);
+
+ home = g_getenv ("HOME");
+ if (!home) {
+ GRL_WARNING ("$HOME not set, cannot open database");
+ return;
+ }
+
+ GRL_DEBUG ("Opening database connection...");
+ db_path = g_strconcat (home, G_DIR_SEPARATOR_S, GRL_SQL_DB, NULL);
+ r = sqlite3_open (db_path, &source->priv->db);
+ if (r) {
+ g_critical ("Failed to open database '%s': %s",
+ db_path, sqlite3_errmsg (source->priv->db));
+ sqlite3_close (source->priv->db);
+ return;
+ }
+ GRL_DEBUG (" OK");
+
+ GRL_DEBUG ("Checking database tables...");
+ r = sqlite3_exec (source->priv->db, GRL_SQL_CREATE_TABLE_STORE,
+ NULL, NULL, &sql_error);
+
+ if (r) {
+ if (sql_error) {
+ GRL_WARNING ("Failed to create database tables: %s", sql_error);
+ sqlite3_free (sql_error);
+ sql_error = NULL;
+ } else {
+ GRL_WARNING ("Failed to create database tables.");
+ }
+ sqlite3_close (source->priv->db);
+ return;
+ }
+ GRL_DEBUG (" OK");
+
+ g_free (db_path);
+}
+
+G_DEFINE_TYPE (GrlMetadataStoreSource, grl_metadata_store_source,
+ GRL_TYPE_METADATA_SOURCE);
+
+/* ======================= Utilities ==================== */
+
+static sqlite3_stmt *
+query_metadata_store (sqlite3 *db,
+ const gchar *source_id,
+ const gchar *media_id)
+{
+ gint r;
+ sqlite3_stmt *sql_stmt = NULL;
+ gchar *sql;
+
+ GRL_DEBUG ("get_metadata");
+
+ sql = g_strdup_printf (GRL_SQL_GET_METADATA, source_id, media_id);
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
+ g_free (sql);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to get metadata: %s", sqlite3_errmsg (db));
+ return NULL;
+ }
+
+ return sql_stmt;
+}
+
+static void
+fill_metadata (GrlMedia *media, GList *keys, sqlite3_stmt *stmt)
+{
+ GList *iter;
+ gint play_count, last_position;
+ gdouble rating;
+ gchar *last_played;
+ gint r;
+
+ while ((r = sqlite3_step (stmt)) == SQLITE_BUSY);
+
+ if (r != SQLITE_ROW) {
+ /* No info in DB for this item, bail out silently */
+ sqlite3_finalize (stmt);
+ return;
+ }
+
+ iter = keys;
+ while (iter) {
+ if (iter->data == GRL_METADATA_KEY_PLAY_COUNT) {
+ play_count = sqlite3_column_int (stmt, STORE_PLAY_COUNT);
+ grl_media_set_play_count (media, play_count);
+ } else if (iter->data == GRL_METADATA_KEY_RATING) {
+ rating = sqlite3_column_double (stmt, STORE_RATING);
+ grl_media_set_rating (media, rating, 5.00);
+ } else if (iter->data == GRL_METADATA_KEY_LAST_PLAYED) {
+ last_played = (gchar *) sqlite3_column_text (stmt, STORE_LAST_PLAYED);
+ grl_media_set_last_played (media, last_played);
+ } else if (iter->data == GRL_METADATA_KEY_LAST_POSITION) {
+ last_position = sqlite3_column_int (stmt, STORE_LAST_POSITION);
+ grl_media_set_last_position (media, last_position);
+ }
+ iter = g_list_next (iter);
+ }
+
+ sqlite3_finalize (stmt);
+}
+
+static const gchar *
+get_column_name_from_key_id (GrlKeyID key_id)
+{
+ static const gchar *col_names[] = {"rating", "last_played", "last_position",
+ "play_count"};
+ if (key_id == GRL_METADATA_KEY_RATING) {
+ return col_names[0];
+ } else if (key_id == GRL_METADATA_KEY_LAST_PLAYED) {
+ return col_names[1];
+ } else if (key_id == GRL_METADATA_KEY_LAST_POSITION) {
+ return col_names[2];
+ } else if (key_id == GRL_METADATA_KEY_PLAY_COUNT) {
+ return col_names[3];
+ } else {
+ return NULL;
+ }
+}
+
+static gboolean
+bind_and_exec (sqlite3 *db,
+ const gchar *sql,
+ const gchar *source_id,
+ const gchar *media_id,
+ GList *col_names,
+ GList *keys,
+ GrlMedia *media)
+{
+ gint r;
+ const gchar *char_value;
+ gint int_value;
+ double double_value;
+ GList *iter_names, *iter_keys;
+ guint count;
+ sqlite3_stmt *stmt;
+
+ /* Create statement from sql */
+ GRL_DEBUG ("%s", sql);
+ r = sqlite3_prepare_v2 (db, sql, strlen (sql), &stmt, NULL);
+
+ if (r != SQLITE_OK) {
+ GRL_WARNING ("Failed to update metadata for '%s - %s': %s",
+ source_id, media_id, sqlite3_errmsg (db));
+ sqlite3_finalize (stmt);
+ return FALSE;
+ }
+
+ /* Bind column values */
+ count = 1;
+ iter_names = col_names;
+ iter_keys = keys;
+ while (iter_names) {
+ if (iter_names->data) {
+ if (iter_keys->data == GRL_METADATA_KEY_RATING) {
+ double_value = grl_media_get_rating (media);
+ sqlite3_bind_double (stmt, count, double_value);
+ } else if (iter_keys->data == GRL_METADATA_KEY_PLAY_COUNT) {
+ int_value = grl_media_get_play_count (media);
+ sqlite3_bind_int (stmt, count, int_value);
+ } else if (iter_keys->data == GRL_METADATA_KEY_LAST_POSITION) {
+ int_value = grl_media_get_last_position (media);
+ sqlite3_bind_int (stmt, count, int_value);
+ } else if (iter_keys->data == GRL_METADATA_KEY_LAST_PLAYED) {
+ char_value = grl_media_get_last_played (media);
+ sqlite3_bind_text (stmt, count, char_value, -1, SQLITE_STATIC);
+ }
+ count++;
+ }
+ iter_keys = g_list_next (iter_keys);
+ iter_names = g_list_next (iter_names);
+ }
+
+ sqlite3_bind_text (stmt, count++, source_id, -1, SQLITE_STATIC);
+ sqlite3_bind_text (stmt, count++, media_id, -1, SQLITE_STATIC);
+
+ /* execute query */
+ while ((r = sqlite3_step (stmt)) == SQLITE_BUSY);
+
+ sqlite3_finalize (stmt);
+
+ return (r == SQLITE_DONE);
+}
+
+static gboolean
+prepare_and_exec_update (sqlite3 *db,
+ const gchar *source_id,
+ const gchar *media_id,
+ GList *col_names,
+ GList *keys,
+ GrlMedia *media)
+{
+ gchar *sql;
+ gint r;
+ GList *iter_names;
+ GString *sql_buf;
+ gchar *sql_set;
+ guint count;
+
+ GRL_DEBUG ("prepare_and_exec_update");
+
+ /* Prepare sql "set" for update query */
+ count = 0;
+ sql_buf = g_string_new ("");
+ iter_names = col_names;
+ while (iter_names) {
+ gchar *col_name = (gchar *) iter_names->data;
+ if (col_name) {
+ if (count > 0) {
+ g_string_append (sql_buf, " AND ");
+ }
+ g_string_append_printf (sql_buf, "%s=?", col_name);
+ count++;
+ }
+ iter_names = g_list_next (iter_names);
+ }
+ sql_set = g_string_free (sql_buf, FALSE);
+
+ /* Execute query */
+ sql = g_strdup_printf (GRL_SQL_UPDATE_METADATA, sql_set);
+ r = bind_and_exec (db, sql, source_id, media_id, col_names, keys, media);
+ g_free (sql);
+ g_free (sql_set);
+
+ return r;
+}
+
+static gboolean
+prepare_and_exec_insert (sqlite3 *db,
+ const gchar *source_id,
+ const gchar *media_id,
+ GList *col_names,
+ GList *keys,
+ GrlMedia *media)
+{
+ gchar *sql;
+ gint r;
+ GList *iter_names;
+ GString *sql_buf_cols, *sql_buf_values;
+ gchar *sql_cols, *sql_values;
+
+ GRL_DEBUG ("prepare_and_exec_insert");
+
+ /* Prepare sql for insert query */
+ sql_buf_cols = g_string_new ("");
+ sql_buf_values = g_string_new ("");
+ iter_names = col_names;
+ while (iter_names) {
+ gchar *col_name = (gchar *) iter_names->data;
+ if (col_name) {
+ g_string_append_printf (sql_buf_cols, "%s, ", col_name);
+ g_string_append (sql_buf_values, "?, ");
+ }
+ iter_names = g_list_next (iter_names);
+ }
+ sql_cols = g_string_free (sql_buf_cols, FALSE);
+ sql_values = g_string_free (sql_buf_values, FALSE);
+
+ /* Execute query */
+ sql = g_strdup_printf (GRL_SQL_INSERT_METADATA, sql_cols, sql_values);
+ r = bind_and_exec (db, sql, source_id, media_id, col_names, keys, media);
+ g_free (sql);
+ g_free (sql_cols);
+ g_free (sql_values);
+
+ return r;
+}
+
+static GList *
+write_keys (sqlite3 *db,
+ const gchar *source_id,
+ const gchar *media_id,
+ GrlMetadataSourceSetMetadataSpec *sms,
+ GError **error)
+{
+ GList *col_names = NULL;
+ GList *iter;
+ GList *failed_keys = NULL;
+ guint supported_keys = 0;
+ gint r;
+
+ /* Get DB column names for each key to be updated */
+ iter = sms->keys;
+ while (iter) {
+ const gchar *col_name = get_column_name_from_key_id (iter->data);
+ if (!col_name) {
+ GRL_WARNING ("Key %" GRL_KEYID_FORMAT " is not supported for "
+ "writing, ignoring...",
+ iter->data);
+ failed_keys = g_list_prepend (failed_keys, iter->data);
+ } else {
+ supported_keys++;
+ }
+ col_names = g_list_prepend (col_names, (gchar *) col_name);
+ iter = g_list_next (iter);
+ }
+ col_names = g_list_reverse (col_names);
+
+ if (supported_keys == 0) {
+ GRL_WARNING ("Failed to update metadata, none of the specified "
+ "keys is writable");
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_SET_METADATA_FAILED,
+ "Failed to update metadata, "
+ "specified keys are not writable");
+ goto done;
+ }
+
+ r = prepare_and_exec_update (db,
+ source_id,
+ media_id,
+ col_names,
+ sms->keys,
+ sms->media);
+
+ if (!r) {
+ GRL_WARNING ("Failed to update metadata for '%s - %s': %s",
+ source_id, media_id, sqlite3_errmsg (db));
+ g_list_free (failed_keys);
+ failed_keys = g_list_copy (sms->keys);
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_SET_METADATA_FAILED,
+ "Failed to update metadata");
+ goto done;
+ }
+
+ if (sqlite3_changes (db) == 0) {
+ /* We have to create the row */
+ r = prepare_and_exec_insert (db,
+ source_id,
+ media_id,
+ col_names,
+ sms->keys,
+ sms->media);
+ }
+
+ if (!r) {
+ GRL_WARNING ("Failed to update metadata for '%s - %s': %s",
+ source_id, media_id, sqlite3_errmsg (db));
+ g_list_free (failed_keys);
+ failed_keys = g_list_copy (sms->keys);
+ *error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_SET_METADATA_FAILED,
+ "Failed to update metadata");
+ goto done;
+ }
+
+ done:
+ g_list_free (col_names);
+ return failed_keys;
+}
+
+/* ================== API Implementation ================ */
+
+static const GList *
+grl_metadata_store_source_supported_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_RATING,
+ GRL_METADATA_KEY_PLAY_COUNT,
+ GRL_METADATA_KEY_LAST_PLAYED,
+ GRL_METADATA_KEY_LAST_POSITION,
+ NULL);
+ }
+ return keys;
+}
+
+static const GList *
+grl_metadata_store_source_writable_keys (GrlMetadataSource *source)
+{
+ static GList *keys = NULL;
+ if (!keys) {
+ keys = grl_metadata_key_list_new (GRL_METADATA_KEY_RATING,
+ GRL_METADATA_KEY_PLAY_COUNT,
+ GRL_METADATA_KEY_LAST_PLAYED,
+ GRL_METADATA_KEY_LAST_POSITION,
+ NULL);
+ }
+ return keys;
+}
+
+static const GList *
+grl_metadata_store_source_key_depends (GrlMetadataSource *source,
+ GrlKeyID key_id)
+{
+ static GList *deps = NULL;
+ if (!deps) {
+ deps = grl_metadata_key_list_new (GRL_METADATA_KEY_ID, NULL);
+ }
+
+ if (key_id == GRL_METADATA_KEY_RATING ||
+ key_id == GRL_METADATA_KEY_PLAY_COUNT ||
+ key_id == GRL_METADATA_KEY_LAST_PLAYED ||
+ key_id == GRL_METADATA_KEY_LAST_POSITION) {
+ return deps;
+ }
+
+ return NULL;
+}
+
+static void
+grl_metadata_store_source_resolve (GrlMetadataSource *source,
+ GrlMetadataSourceResolveSpec *rs)
+{
+ GRL_DEBUG ("grl_metadata_store_source_resolve");
+
+ const gchar *source_id, *media_id;
+ sqlite3_stmt *stmt;
+ GError *error = NULL;
+
+ source_id = grl_media_get_source (rs->media);
+ media_id = grl_media_get_id (rs->media);
+
+ /* We need the source id */
+ if (!source_id) {
+ GRL_WARNING ("Failed to resolve metadata: source-id not available");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_RESOLVE_FAILED,
+ "source-id not available, cannot resolve metadata.");
+ rs->callback (rs->source, rs->media, rs->user_data, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* Special case for root categories */
+ if (!media_id) {
+ media_id = "";
+ }
+
+ stmt = query_metadata_store (GRL_METADATA_STORE_SOURCE (source)->priv->db,
+ source_id, media_id);
+ if (stmt) {
+ fill_metadata (rs->media, rs->keys, stmt);
+ rs->callback (rs->source, rs->media, rs->user_data, NULL);
+ } else {
+ GRL_WARNING ("Failed to resolve metadata");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_RESOLVE_FAILED,
+ "Failed to resolve metadata.");
+ rs->callback (rs->source, rs->media, rs->user_data, error);
+ g_error_free (error);
+ }
+}
+
+static void
+grl_metadata_store_source_set_metadata (GrlMetadataSource *source,
+ GrlMetadataSourceSetMetadataSpec *sms)
+{
+ GRL_DEBUG ("grl_metadata_store_source_set_metadata");
+
+ const gchar *media_id, *source_id;
+ GError *error = NULL;
+ GList *failed_keys = NULL;
+
+ source_id = grl_media_get_source (sms->media);
+ media_id = grl_media_get_id (sms->media);
+
+ /* We need the source id */
+ if (!source_id) {
+ GRL_WARNING ("Failed to update metadata: source-id not available");
+ error = g_error_new (GRL_CORE_ERROR,
+ GRL_CORE_ERROR_SET_METADATA_FAILED,
+ "source-id not available, cannot update metadata.");
+ failed_keys = g_list_copy (sms->keys);
+ } else {
+ /* Special case for root categories */
+ if (!media_id) {
+ media_id = "";
+ }
+
+ failed_keys = write_keys (GRL_METADATA_STORE_SOURCE (source)->priv->db,
+ source_id, media_id, sms, &error);
+ }
+
+ sms->callback (sms->source, sms->media, failed_keys, sms->user_data, error);
+
+ if (error) {
+ g_error_free (error);
+ }
+ g_list_free (failed_keys);
+}
diff --git a/src/metadata/metadata-store/grl-metadata-store.h b/src/metadata/metadata-store/grl-metadata-store.h
new file mode 100644
index 0000000..47df494
--- /dev/null
+++ b/src/metadata/metadata-store/grl-metadata-store.h
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2010 Igalia S.L.
+ *
+ * Contact: Iago Toral Quiroga <itoral igalia 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_METADATA_STORE_SOURCE_H_
+#define _GRL_METADATA_STORE_SOURCE_H_
+
+#include <grilo.h>
+
+#define GRL_METADATA_STORE_SOURCE_TYPE \
+ (grl_metadata_store_source_get_type ())
+
+#define GRL_METADATA_STORE_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
+ GRL_METADATA_STORE_SOURCE_TYPE, \
+ GrlMetadataStoreSource))
+
+#define GRL_IS_METADATA_STORE_SOURCE(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GRL_METADATA_STORE_SOURCE_TYPE))
+
+#define GRL_METADATA_STORE_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GRL_METADATA_STORE_SOURCE_TYPE, \
+ GrlMetadataStoreSourceClass))
+
+#define GRL_IS_METADATA_STORE_SOURCE_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), \
+ GRL_METADATA_STORE_SOURCE_TYPE))
+
+#define GRL_METADATA_STORE_SOURCE_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GRL_METADATA_STORE_SOURCE_TYPE, \
+ GrlMetadataStoreSourceClass))
+
+typedef struct _GrlMetadataStorePrivate GrlMetadataStorePrivate;
+typedef struct _GrlMetadataStoreSource GrlMetadataStoreSource;
+
+struct _GrlMetadataStoreSource {
+
+ GrlMetadataSource parent;
+
+ /*< private >*/
+ GrlMetadataStorePrivate *priv;
+};
+
+typedef struct _GrlMetadataStoreSourceClass GrlMetadataStoreSourceClass;
+
+struct _GrlMetadataStoreSourceClass {
+
+ GrlMetadataSourceClass parent_class;
+
+};
+
+GType grl_metadata_store_source_get_type (void);
+
+#endif /* _GRL_METADATA_STORE_SOURCE_H_ */
diff --git a/src/metadata/metadata-store/grl-metadata-store.xml b/src/metadata/metadata-store/grl-metadata-store.xml
new file mode 100644
index 0000000..b8bbbb8
--- /dev/null
+++ b/src/metadata/metadata-store/grl-metadata-store.xml
@@ -0,0 +1,9 @@
+<plugin>
+ <info>
+ <name>Metadata Store</name>
+ <description>A plugin for storing extra metadata information</description>
+ <author>Igalia S.L.</author>
+ <license>LGPL</license>
+ <site>http://www.igalia.com</site>
+ </info>
+</plugin>
diff --git a/src/podcasts/Makefile.am b/src/podcasts/Makefile.am
deleted file mode 100644
index 2383042..0000000
--- a/src/podcasts/Makefile.am
+++ /dev/null
@@ -1,38 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Iago Toral Quiroga <itoral igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlpodcasts.la
-
-libgrlpodcasts_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GRLNET_CFLAGS) \
- $(XML_CFLAGS) \
- $(SQLITE_CFLAGS)
-
-libgrlpodcasts_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GRLNET_LIBS) \
- $(XML_LIBS) \
- $(SQLITE_LIBS)
-
-libgrlpodcasts_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlpodcasts_la_SOURCES = grl-podcasts.c grl-podcasts.h
-
-libdir = $(GRL_PLUGINS_DIR)
-podcastsxmldir = $(GRL_PLUGINS_CONF_DIR)
-podcastsxml_DATA = $(PODCASTS_PLUGIN_ID).xml
-
-EXTRA_DIST = $(podcastsxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/podcasts/TODO b/src/podcasts/TODO
deleted file mode 100644
index 12f3311..0000000
--- a/src/podcasts/TODO
+++ /dev/null
@@ -1,5 +0,0 @@
-- IGN podcasts contains items with no enclosure. These should be skipped.
-- Support more tags (like author, site, etc)
-- Parsing and inserting in the DB seems to block long enough, split the parsing to handle only
- one element in each go.
-- Implement support for remove() -- Needs support in core first
diff --git a/src/podcasts/grl-podcasts.c b/src/podcasts/grl-podcasts.c
deleted file mode 100644
index a4d5ad3..0000000
--- a/src/podcasts/grl-podcasts.c
+++ /dev/null
@@ -1,1636 +0,0 @@
-/*
- * Copyright (C) 2010, 2011 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
-#include <net/grl-net.h>
-#include <libxml/xpath.h>
-#include <sqlite3.h>
-#include <string.h>
-
-#include "grl-podcasts.h"
-
-#define GRL_PODCASTS_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_PODCASTS_SOURCE_TYPE, \
- GrlPodcastsPrivate))
-
-#define GRL_ROOT_TITLE "Podcasts"
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT podcasts_log_domain
-GRL_LOG_DOMAIN_STATIC(podcasts_log_domain);
-
-/* --- Database --- */
-
-#define GRL_SQL_DB ".grl-podcasts"
-
-#define GRL_SQL_CREATE_TABLE_PODCASTS \
- "CREATE TABLE IF NOT EXISTS podcasts (" \
- "id INTEGER PRIMARY KEY AUTOINCREMENT," \
- "title TEXT," \
- "url TEXT," \
- "desc TEXT," \
- "last_refreshed DATE)"
-
-#define GRL_SQL_CREATE_TABLE_STREAMS \
- "CREATE TABLE IF NOT EXISTS streams ( " \
- "podcast INTEGER REFERENCES podcasts (id), " \
- "url TEXT, " \
- "title TEXT, " \
- "length INTEGER, " \
- "mime TEXT, " \
- "date TEXT, " \
- "desc TEXT)"
-
-#define GRL_SQL_GET_PODCASTS \
- "SELECT p.*, count(s.podcast <> '') " \
- "FROM podcasts p LEFT OUTER JOIN streams s " \
- " ON p.id = s.podcast " \
- "GROUP BY p.id " \
- "LIMIT %u OFFSET %u"
-
-#define GRL_SQL_GET_PODCASTS_BY_QUERY \
- "SELECT p.*, count(s.podcast <> '') " \
- "FROM podcasts p LEFT OUTER JOIN streams s " \
- " ON p.id = s.podcast " \
- "WHERE %s " \
- "GROUP BY p.id " \
- "LIMIT %u OFFSET %u"
-
-#define GRL_SQL_GET_PODCAST_BY_ID \
- "SELECT * FROM podcasts " \
- "WHERE id='%s' " \
- "LIMIT 1"
-
-#define GRL_SQL_STORE_PODCAST \
- "INSERT INTO podcasts " \
- "(url, title, desc) " \
- "VALUES (?, ?, ?)"
-
-#define GRL_SQL_REMOVE_PODCAST \
- "DELETE FROM podcasts " \
- "WHERE id='%s'"
-
-#define GRL_SQL_REMOVE_STREAM \
- "DELETE FROM streams " \
- "WHERE url='%s'"
-
-#define GRL_SQL_STORE_STREAM \
- "INSERT INTO streams " \
- "(podcast, url, title, length, mime, date, desc) " \
- "VALUES (?, ?, ?, ?, ?, ?, ?)"
-
-#define GRL_SQL_DELETE_PODCAST_STREAMS \
- "DELETE FROM streams WHERE podcast='%s'"
-
-#define GRL_SQL_GET_PODCAST_STREAMS \
- "SELECT * FROM streams " \
- "WHERE podcast='%s' " \
- "LIMIT %u OFFSET %u"
-
-#define GRL_SQL_GET_PODCAST_STREAMS_BY_TEXT \
- "SELECT s.* " \
- "FROM streams s LEFT OUTER JOIN podcasts p " \
- " ON s.podcast = p.id " \
- "WHERE s.title LIKE '%%%s%%' OR s.desc LIKE '%%%s%%' " \
- " OR p.title LIKE '%%%s%%' OR p.desc LIKE '%%%s%%' " \
- "LIMIT %u OFFSET %u"
-
-#define GRL_SQL_GET_PODCAST_STREAMS_ALL \
- "SELECT * FROM streams " \
- "LIMIT %u OFFSET %u"
-
-#define GRL_SQL_GET_PODCAST_STREAM \
- "SELECT * FROM streams " \
- "WHERE url='%s' " \
- "LIMIT 1"
-
-#define GRL_SQL_TOUCH_PODCAST \
- "UPDATE podcasts " \
- "SET last_refreshed='%s' " \
- "WHERE id='%s'"
-
-/* --- Other --- */
-
-#define CACHE_DURATION (24 * 60 * 60)
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID PODCASTS_PLUGIN_ID
-
-#define SOURCE_ID "grl-podcasts"
-#define SOURCE_NAME "Podcasts"
-#define SOURCE_DESC "A source for browsing podcasts"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-enum {
- PODCAST_ID = 0,
- PODCAST_TITLE,
- PODCAST_URL,
- PODCAST_DESC,
- PODCAST_LAST_REFRESHED,
- PODCAST_LAST,
-};
-
-enum {
- STREAM_PODCAST = 0,
- STREAM_URL,
- STREAM_TITLE,
- STREAM_LENGTH,
- STREAM_MIME,
- STREAM_DATE,
- STREAM_DESC,
-};
-
-typedef void (*AsyncReadCbFunc) (gchar *data, gpointer user_data);
-
-typedef struct {
- AsyncReadCbFunc callback;
- gchar *url;
- gpointer user_data;
-} AsyncReadCb;
-
-typedef struct {
- gchar *id;
- gchar *url;
- gchar *title;
- gchar *published;
- gchar *duration;
- gchar *summary;
- gchar *mime;
-} Entry;
-
-struct _GrlPodcastsPrivate {
- sqlite3 *db;
- GrlNetWc *wc;
- gboolean notify_changes;
-};
-
-typedef struct {
- GrlMediaSource *source;
- guint operation_id;
- const gchar *media_id;
- guint skip;
- guint count;
- const gchar *text;
- GrlMediaSourceResultCb callback;
- guint error_code;
- gboolean is_query;
- gpointer user_data;
-} OperationSpec;
-
-typedef struct {
- OperationSpec *os;
- xmlDocPtr doc;
- xmlXPathContextPtr xpathCtx;
- xmlXPathObjectPtr xpathObj;
- guint parse_count;
- guint parse_index;
- guint parse_valid_index;
- GrlMedia *last_media;
-} OperationSpecParse;
-
-static GrlPodcastsSource *grl_podcasts_source_new (void);
-
-static void grl_podcasts_source_finalize (GObject *plugin);
-
-static const GList *grl_podcasts_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_podcasts_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-static void grl_podcasts_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-static void grl_podcasts_source_query (GrlMediaSource *source,
- GrlMediaSourceQuerySpec *qs);
-static void grl_podcasts_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms);
-static void grl_podcasts_source_store (GrlMediaSource *source,
- GrlMediaSourceStoreSpec *ss);
-static void grl_podcasts_source_remove (GrlMediaSource *source,
- GrlMediaSourceRemoveSpec *rs);
-static gboolean grl_podcasts_source_notify_change_start (GrlMediaSource *source,
- GError **error);
-static gboolean grl_podcasts_source_notify_change_stop (GrlMediaSource *source,
- GError **error);
-
-/* =================== Podcasts Plugin =============== */
-
-static gboolean
-grl_podcasts_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (podcasts_log_domain, "podcasts");
-
- GRL_DEBUG ("podcasts_plugin_init");
-
- GrlPodcastsSource *source = grl_podcasts_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_podcasts_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Podcasts GObject ================ */
-
-static GrlPodcastsSource *
-grl_podcasts_source_new (void)
-{
- GRL_DEBUG ("grl_podcasts_source_new");
- return g_object_new (GRL_PODCASTS_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_podcasts_source_class_init (GrlPodcastsSourceClass * klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
-
- gobject_class->finalize = grl_podcasts_source_finalize;
-
- source_class->browse = grl_podcasts_source_browse;
- source_class->search = grl_podcasts_source_search;
- source_class->query = grl_podcasts_source_query;
- source_class->metadata = grl_podcasts_source_metadata;
- source_class->store = grl_podcasts_source_store;
- source_class->remove = grl_podcasts_source_remove;
- source_class->notify_change_start = grl_podcasts_source_notify_change_start;
- source_class->notify_change_stop = grl_podcasts_source_notify_change_stop;
-
- metadata_class->supported_keys = grl_podcasts_source_supported_keys;
-
- g_type_class_add_private (klass, sizeof (GrlPodcastsPrivate));
-}
-
-static void
-grl_podcasts_source_init (GrlPodcastsSource *source)
-{
- gint r;
- const gchar *home;
- gchar *db_path;
- gchar *sql_error = NULL;
-
- source->priv = GRL_PODCASTS_GET_PRIVATE (source);
-
- home = g_getenv ("HOME");
- if (!home) {
- GRL_WARNING ("$HOME not set, cannot open database");
- return;
- }
-
- GRL_DEBUG ("Opening database connection...");
- db_path = g_strconcat (home, G_DIR_SEPARATOR_S, GRL_SQL_DB, NULL);
- r = sqlite3_open (db_path, &source->priv->db);
- if (r) {
- g_critical ("Failed to open database '%s': %s",
- db_path, sqlite3_errmsg (source->priv->db));
- sqlite3_close (source->priv->db);
- return;
- }
- GRL_DEBUG (" OK");
-
- GRL_DEBUG ("Checking database tables...");
- r = sqlite3_exec (source->priv->db, GRL_SQL_CREATE_TABLE_PODCASTS,
- NULL, NULL, &sql_error);
-
- if (!r) {
- /* TODO: if this fails, sqlite stays in an unreliable state fix that */
- r = sqlite3_exec (source->priv->db, GRL_SQL_CREATE_TABLE_STREAMS,
- NULL, NULL, &sql_error);
- }
- if (r) {
- if (sql_error) {
- GRL_WARNING ("Failed to create database tables: %s", sql_error);
- sqlite3_free (sql_error);
- sql_error = NULL;
- } else {
- GRL_WARNING ("Failed to create database tables.");
- }
- sqlite3_close (source->priv->db);
- return;
- }
- GRL_DEBUG (" OK");
-
- g_free (db_path);
-}
-
-G_DEFINE_TYPE (GrlPodcastsSource, grl_podcasts_source, GRL_TYPE_MEDIA_SOURCE);
-
-static void
-grl_podcasts_source_finalize (GObject *object)
-{
- GrlPodcastsSource *source;
-
- GRL_DEBUG ("grl_podcasts_source_finalize");
-
- source = GRL_PODCASTS_SOURCE (object);
-
- if (source->priv->wc)
- g_object_unref (source->priv->wc);
-
- sqlite3_close (source->priv->db);
-
- G_OBJECT_CLASS (grl_podcasts_source_parent_class)->finalize (object);
-}
-
-/* ======================= Utilities ==================== */
-
-static void
-print_entry (Entry *entry)
-{
- g_print ("Entry Information:\n");
- g_print (" ID: %s\n", entry->id);
- g_print (" Title: %s\n", entry->title);
- g_print (" Date: %s\n", entry->published);
- g_print (" Duration: %s\n", entry->duration);
- g_print (" Summary: %s\n", entry->summary);
- g_print (" URL: %s\n", entry->url);
- g_print (" Mime: %s\n", entry->mime);
-}
-
-static void
-free_entry (Entry *entry)
-{
- g_free (entry->id);
- g_free (entry->title);
- g_free (entry->published);
- g_free (entry->summary);
- g_free (entry->url);
- g_free (entry->mime);
-}
-
-static void
-read_done_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- AsyncReadCb *arc = (AsyncReadCb *) user_data;
- GError *wc_error = NULL;
- gchar *content = NULL;
-
- GRL_DEBUG (" Done");
-
- grl_net_wc_request_finish (GRL_NET_WC (source_object),
- res,
- &content,
- NULL,
- &wc_error);
- if (wc_error) {
- GRL_WARNING ("Failed to open '%s': %s", arc->url, wc_error->message);
- g_error_free (wc_error);
- } else {
- arc->callback (content, arc->user_data);
- }
- g_free (arc->url);
- g_slice_free (AsyncReadCb, arc);
-}
-
-static void
-read_url_async (GrlPodcastsSource *source,
- const gchar *url,
- AsyncReadCbFunc callback,
- gpointer user_data)
-{
- AsyncReadCb *arc;
-
- GRL_DEBUG ("Opening async '%s'", url);
-
- arc = g_slice_new0 (AsyncReadCb);
- arc->url = g_strdup (url);
- arc->callback = callback;
- arc->user_data = user_data;
-
- /* We would need a different Wc if we change of URL.
- * In this case, as we don't know the previous URL,
- * we ditch the Wc and create another. It's cheap.
- */
- if (!source->priv->wc)
- g_object_unref (source->priv->wc);
-
- source->priv->wc = grl_net_wc_new ();
- grl_net_wc_request_async (source->priv->wc, url, NULL, read_done_cb, arc);
-}
-
-static gint
-duration_to_seconds (const gchar *str)
-{
- gint seconds = 0;
- gchar **parts;
- gint i;
- guint multiplier = 1;
-
- if (!str || str[0] == '\0') {
- return 0;
- }
-
- parts = g_strsplit (str, ":", 3);
-
- /* Get last portion (seconds) */
- i = 0;
- while (parts[i]) i++;
- if (i == 0) {
- g_strfreev (parts);
- return 0;
- } else {
- i--;
- }
-
- do {
- seconds += atoi (parts[i]) * multiplier;
- multiplier *= 60;
- i--;
- } while (i >= 0);
-
- g_strfreev (parts);
-
- return seconds;
-}
-
-static gboolean
-mime_is_video (const gchar *mime)
-{
- return mime && strstr (mime, "video") != NULL;
-}
-
-static gboolean
-mime_is_audio (const gchar *mime)
-{
- return mime && strstr (mime, "audio") != NULL;
-}
-
-static gchar *
-get_site_from_url (const gchar *url)
-{
- gchar *p;
-
- if (g_str_has_prefix (url, "file://")) {
- return NULL;
- }
-
- p = strstr (url, "://");
- if (!p) {
- return NULL;
- } else {
- p += 3;
- }
-
- while (*p != '/') p++;
-
- return g_strndup (url, p - url);
-}
-
-static GrlMedia *
-build_media (GrlMedia *content,
- gboolean is_podcast,
- const gchar *id,
- const gchar *title,
- const gchar *url,
- const gchar *desc,
- const gchar *mime,
- const gchar *date,
- guint duration,
- guint childcount)
-{
- GrlMedia *media = NULL;
- gchar *site;
-
- if (content) {
- media = content;
- }
-
- if (is_podcast) {
- if (!media) {
- media = GRL_MEDIA (grl_media_box_new ());
- }
-
- grl_media_set_id (media, id);
- if (desc)
- grl_media_set_description (media, desc);
- grl_media_box_set_childcount (GRL_MEDIA_BOX (media), childcount);
- } else {
- if (!media) {
- if (mime_is_audio (mime)) {
- media = grl_media_audio_new ();
- } else if (mime_is_video (mime)) {
- media = grl_media_video_new ();
- } else {
- media = grl_media_new ();
- }
- }
-
- grl_media_set_id (media, url);
- if (date)
- grl_media_set_date (media, date);
- if (desc)
- grl_media_set_description (media, desc);
- if (mime)
- grl_media_set_mime (media, mime);
- if (duration > 0) {
- grl_media_set_duration (media, duration);
- }
- }
-
- grl_media_set_title (media, title);
- grl_media_set_url (media, url);
-
- site = get_site_from_url (url);
- if (site) {
- grl_media_set_site (media, site);
- g_free (site);
- }
-
- return media;
-}
-
-static GrlMedia *
-build_media_from_entry (Entry *entry)
-{
- GrlMedia *media;
- gint duration;
-
- duration = duration_to_seconds (entry->duration);
- media = build_media (NULL, FALSE,
- entry->url, entry->title, entry->url,
- entry->summary, entry->mime, entry->published,
- duration, 0);
- return media;
-}
-
-static GrlMedia *
-build_media_from_stmt (GrlMedia *content,
- sqlite3_stmt *sql_stmt,
- gboolean is_podcast)
-{
- GrlMedia *media;
- gchar *id;
- gchar *title;
- gchar *url;
- gchar *desc;
- gchar *mime;
- gchar *date;
- guint duration;
- guint childcount;
-
- if (is_podcast) {
- id = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_ID);
- title = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_TITLE);
- url = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_URL);
- desc = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_DESC);
- childcount = (guint) sqlite3_column_int (sql_stmt, PODCAST_LAST);
- media = build_media (content, is_podcast,
- id, title, url, desc, NULL, NULL, 0, childcount);
- } else {
- mime = (gchar *) sqlite3_column_text (sql_stmt, STREAM_MIME);
- url = (gchar *) sqlite3_column_text (sql_stmt, STREAM_URL);
- title = (gchar *) sqlite3_column_text (sql_stmt, STREAM_TITLE);
- date = (gchar *) sqlite3_column_text (sql_stmt, STREAM_DATE);
- desc = (gchar *) sqlite3_column_text (sql_stmt, STREAM_DESC);
- duration = sqlite3_column_int (sql_stmt, STREAM_LENGTH);
- media = build_media (content, is_podcast,
- url, title, url, desc, mime, date, duration, 0);
- }
-
- return media;
-}
-
-static void
-produce_podcast_contents_from_db (OperationSpec *os)
-{
- sqlite3 *db;
- gchar *sql;
- sqlite3_stmt *sql_stmt = NULL;
- GList *iter, *medias = NULL;
- guint count = 0;
- GrlMedia *media;
- gint r;
- GError *error = NULL;
-
- GRL_DEBUG ("produce_podcast_contents_from_db");
-
- db = GRL_PODCASTS_SOURCE (os->source)->priv->db;
- /* Check if searching or browsing */
- if (os->is_query) {
- if (os->text) {
- /* Search text */
- sql = g_strdup_printf (GRL_SQL_GET_PODCAST_STREAMS_BY_TEXT,
- os->text, os->text, os->text, os->text,
- os->count, os->skip);
- } else {
- /* Return all */
- sql = g_strdup_printf (GRL_SQL_GET_PODCAST_STREAMS_ALL,
- os->count, os->skip);
- }
- } else {
- sql = g_strdup_printf (GRL_SQL_GET_PODCAST_STREAMS,
- os->media_id, os->count, os->skip);
- }
- GRL_DEBUG ("%s", sql);
- r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to retrieve podcast streams: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to retrieve podcast streams");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
- return;
- }
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- while (r == SQLITE_ROW) {
- media = build_media_from_stmt (NULL, sql_stmt, FALSE);
- medias = g_list_prepend (medias, media);
- count++;
- r = sqlite3_step (sql_stmt);
- }
-
- if (r != SQLITE_DONE) {
- GRL_WARNING ("Failed to retrive podcast streams: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to retrieve podcast streams");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
- sqlite3_finalize (sql_stmt);
- return;
- }
-
- sqlite3_finalize (sql_stmt);
-
- if (count > 0) {
- medias = g_list_reverse (medias);
- iter = medias;
- while (iter) {
- media = GRL_MEDIA (iter->data);
- os->callback (os->source,
- os->operation_id,
- media,
- --count,
- os->user_data,
- NULL);
- iter = g_list_next (iter);
- }
- g_list_free (medias);
- } else {
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
- }
-}
-
-static void
-remove_podcast_streams (sqlite3 *db, const gchar *podcast_id, GError **error)
-{
- gchar *sql;
- gchar *sql_error;
- gint r;
-
- sql = g_strdup_printf (GRL_SQL_DELETE_PODCAST_STREAMS, podcast_id);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_exec (db, sql, NULL, NULL, &sql_error);
- g_free (sql);
- if (r) {
- GRL_WARNING ("Failed to remove podcast streams cache: %s", sql_error);
- *error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_REMOVE_FAILED,
- "Failed to remove podcast streams");
- sqlite3_free (error);
- }
-}
-
-static void
-remove_podcast (GrlPodcastsSource *podcasts_source,
- const gchar *podcast_id,
- GError **error)
-{
- gint r;
- gchar *sql_error;
- gchar *sql;
-
- GRL_DEBUG ("remove_podcast");
-
- remove_podcast_streams (podcasts_source->priv->db, podcast_id, error);
- if (*error) {
- return;
- }
-
- sql = g_strdup_printf (GRL_SQL_REMOVE_PODCAST, podcast_id);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_exec (podcasts_source->priv->db, sql, NULL, NULL, &sql_error);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to remove podcast '%s': %s", podcast_id, sql_error);
- g_set_error_literal (error,
- GRL_CORE_ERROR,
- GRL_CORE_ERROR_REMOVE_FAILED,
- "Failed to remove podcast");
- sqlite3_free (sql_error);
- } else if (podcasts_source->priv->notify_changes) {
- grl_media_source_notify_change (GRL_MEDIA_SOURCE (podcasts_source),
- NULL,
- GRL_CONTENT_REMOVED,
- TRUE);
- }
-}
-
-static void
-remove_stream (GrlPodcastsSource *podcasts_source,
- const gchar *url,
- GError **error)
-{
- gint r;
- gchar *sql_error;
- gchar *sql;
-
- GRL_DEBUG ("remove_stream");
-
- sql = g_strdup_printf (GRL_SQL_REMOVE_STREAM, url);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_exec (podcasts_source->priv->db, sql, NULL, NULL, &sql_error);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to remove podcast stream '%s': %s", url, sql_error);
- g_set_error_literal (error,
- GRL_CORE_ERROR,
- GRL_CORE_ERROR_REMOVE_FAILED,
- "Failed to remove podcast stream");
- sqlite3_free (sql_error);
- } else if (podcasts_source->priv->notify_changes) {
- grl_media_source_notify_change (GRL_MEDIA_SOURCE (podcasts_source),
- NULL,
- GRL_CONTENT_REMOVED,
- TRUE);
- }
-}
-
-static void
-store_podcast (GrlPodcastsSource *podcasts_source,
- GrlMedia *podcast,
- GError **error)
-{
- gint r;
- sqlite3_stmt *sql_stmt = NULL;
- const gchar *title;
- const gchar *url;
- const gchar *desc;
- gchar *id;
-
- GRL_DEBUG ("store_podcast");
-
- title = grl_media_get_title (podcast);
- url = grl_media_get_url (podcast);
- desc = grl_media_get_description (podcast);
-
- GRL_DEBUG ("%s", GRL_SQL_STORE_PODCAST);
- r = sqlite3_prepare_v2 (podcasts_source->priv->db,
- GRL_SQL_STORE_PODCAST,
- strlen (GRL_SQL_STORE_PODCAST),
- &sql_stmt, NULL);
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to store podcast '%s': %s", title,
- sqlite3_errmsg (podcasts_source->priv->db));
- g_set_error (error,
- GRL_CORE_ERROR,
- GRL_CORE_ERROR_STORE_FAILED,
- "Failed to store podcast '%s'", title);
- return;
- }
-
- sqlite3_bind_text (sql_stmt, 1, url, -1, SQLITE_STATIC);
- sqlite3_bind_text (sql_stmt, 2, title, -1, SQLITE_STATIC);
- if (desc) {
- sqlite3_bind_text (sql_stmt, 3, desc, -1, SQLITE_STATIC);
- } else {
- sqlite3_bind_text (sql_stmt, 3, "", -1, SQLITE_STATIC);
- }
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- if (r != SQLITE_DONE) {
- GRL_WARNING ("Failed to store podcast '%s': %s", title,
- sqlite3_errmsg (podcasts_source->priv->db));
- g_set_error (error,
- GRL_CORE_ERROR,
- GRL_CORE_ERROR_STORE_FAILED,
- "Failed to store podcast '%s'", title);
- sqlite3_finalize (sql_stmt);
- return;
- }
-
- sqlite3_finalize (sql_stmt);
-
- id = g_strdup_printf ("%llu",
- sqlite3_last_insert_rowid (podcasts_source->priv->db));
- grl_media_set_id (podcast, id);
- g_free (id);
-
- if (podcasts_source->priv->notify_changes) {
- grl_media_source_notify_change (GRL_MEDIA_SOURCE (podcasts_source),
- NULL,
- GRL_CONTENT_ADDED,
- FALSE);
- }
-}
-
-static void
-store_stream (sqlite3 *db, const gchar *podcast_id, Entry *entry)
-{
- gint r;
- guint seconds;
- sqlite3_stmt *sql_stmt = NULL;
-
- if (!entry->url || entry->url[0] == '\0') {
- GRL_DEBUG ("Podcast stream has no URL, skipping");
- return;
- }
-
- seconds = duration_to_seconds (entry->duration);
- GRL_DEBUG ("%s", GRL_SQL_STORE_STREAM);
- r = sqlite3_prepare_v2 (db,
- GRL_SQL_STORE_STREAM,
- strlen (GRL_SQL_STORE_STREAM),
- &sql_stmt, NULL);
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to store podcast stream '%s': %s",
- entry->url, sqlite3_errmsg (db));
- return;
- }
-
- sqlite3_bind_text (sql_stmt, 1, podcast_id, -1, SQLITE_STATIC);
- sqlite3_bind_text (sql_stmt, 2, entry->url, -1, SQLITE_STATIC);
- sqlite3_bind_text (sql_stmt, 3, entry->title, -1, SQLITE_STATIC);
- sqlite3_bind_int (sql_stmt, 4, seconds);
- sqlite3_bind_text (sql_stmt, 5, entry->mime, -1, SQLITE_STATIC);
- sqlite3_bind_text (sql_stmt, 6, entry->published, -1, SQLITE_STATIC);
- sqlite3_bind_text (sql_stmt, 7, entry->summary, -1, SQLITE_STATIC);
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- if (r != SQLITE_DONE) {
- GRL_WARNING ("Failed to store podcast stream '%s': %s",
- entry->url, sqlite3_errmsg (db));
- }
-
- sqlite3_finalize (sql_stmt);
-}
-
-static void
-parse_entry (xmlDocPtr doc, xmlNodePtr entry, Entry *data)
-{
- xmlNodePtr node;
- node = entry->xmlChildrenNode;
- while (node) {
- if (!xmlStrcmp (node->name, (const xmlChar *) "title")) {
- data->title =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "enclosure")) {
- data->id = (gchar *) xmlGetProp (node, (xmlChar *) "url");
- data->url = g_strdup (data->id);
- data->mime = (gchar *) xmlGetProp (node, (xmlChar *) "type");
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "summary")) {
- data->summary =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "pubDate")) {
- data->published =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
- } else if (!xmlStrcmp (node->name, (const xmlChar *) "duration")) {
- data->duration =
- (gchar *) xmlNodeListGetString (doc, node->xmlChildrenNode, 1);
- }
- node = node->next;
- }
-}
-
-static void
-touch_podcast (sqlite3 *db, const gchar *podcast_id)
-{
- gint r;
- gchar *sql, *sql_error;
- GTimeVal now;
- gchar *now_str;
-
- GRL_DEBUG ("touch_podcast");
-
- g_get_current_time (&now);
- now_str = g_time_val_to_iso8601 (&now);
-
- sql = g_strdup_printf (GRL_SQL_TOUCH_PODCAST, now_str, podcast_id);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_exec (db, sql, NULL, NULL, &sql_error);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to touch podcast, '%s': %s", podcast_id, sql_error);
- sqlite3_free (sql_error);
- return;
- }
-}
-
-static gboolean
-parse_entry_idle (gpointer user_data)
-{
- OperationSpecParse *osp = (OperationSpecParse *) user_data;
- xmlNodeSetPtr nodes;
- guint remaining;
- GrlMedia *media;
-
- nodes = osp->xpathObj->nodesetval;
-
- /* Parse entry */
- Entry *entry = g_slice_new0 (Entry);
- if (nodes->nodeTab) {
- parse_entry (osp->doc, nodes->nodeTab[osp->parse_index], entry);
- }
- if (0) print_entry (entry);
-
- /* Check if entry is valid */
- if (!entry->url || entry->url[0] == '\0') {
- GRL_DEBUG ("Podcast stream has no URL, skipping");
- } else {
- /* Provide results to user as fast as possible */
- if (osp->parse_valid_index >= osp->os->skip &&
- osp->parse_valid_index < osp->os->skip + osp->os->count) {
- media = build_media_from_entry (entry);
- remaining = osp->os->skip + osp->os->count - osp->parse_valid_index - 1;
-
- /* Hack: if we emit the last result now the UI may request more results
- right away while we are still parsing the XML, so we keep the last
- result until we are done processing the whole feed, this way when
- the next query arrives all the stuff is stored in the database
- and the query can be resolved normally */
- if (remaining > 0) {
- osp->os->callback (osp->os->source,
- osp->os->operation_id,
- media,
- remaining,
- osp->os->user_data,
- NULL);
- } else {
- osp->last_media = media;
- }
- }
-
- osp->parse_valid_index++;
-
- /* And store stream in database cache */
- store_stream (GRL_PODCASTS_SOURCE (osp->os->source)->priv->db,
- osp->os->media_id, entry);
- }
-
- osp->parse_index++;
- free_entry (entry);
-
- if (osp->parse_index >= osp->parse_count) {
- /* Send last result */
- osp->os->callback (osp->os->source,
- osp->os->operation_id,
- osp->last_media,
- 0,
- osp->os->user_data,
- NULL);
- /* Notify about changes */
- if (GRL_PODCASTS_SOURCE (osp->os->source)->priv->notify_changes) {
- media = grl_media_box_new ();
- grl_media_set_id (media, osp->os->media_id);
- grl_media_source_notify_change (osp->os->source,
- media,
- GRL_CONTENT_CHANGED,
- FALSE);
- g_object_unref (media);
- }
- g_slice_free (OperationSpec, osp->os);
- xmlXPathFreeObject (osp->xpathObj);
- xmlXPathFreeContext (osp->xpathCtx);
- xmlFreeDoc (osp->doc);
- g_slice_free (OperationSpecParse, osp);
- }
-
- return osp->parse_index < osp->parse_count;
-}
-
-static void
-parse_feed (OperationSpec *os, const gchar *str, GError **error)
-{
- GrlMedia *podcast = NULL;
- xmlDocPtr doc = NULL;
- xmlXPathContextPtr xpathCtx = NULL;
- xmlXPathObjectPtr xpathObj = NULL;
- guint stream_count;
-
- GRL_DEBUG ("parse_feed");
-
- doc = xmlParseDoc ((xmlChar *) str);
- if (!doc) {
- *error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to parse podcast contents");
- goto free_resources;
- }
-
- /* Get feed stream list */
- xpathCtx = xmlXPathNewContext (doc);
- if (!xpathCtx) {
- *error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to parse podcast contents");
- goto free_resources;
- }
-
- xpathObj = xmlXPathEvalExpression ((xmlChar *) "/rss/channel/item",
- xpathCtx);
- if(xpathObj == NULL) {
- *error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to parse podcast contents");
- goto free_resources;
- }
-
- /* Feed is ok, let's process it */
-
- /* First, remove old entries for this podcast */
- remove_podcast_streams (GRL_PODCASTS_SOURCE (os->source)->priv->db,
- os->media_id, error);
- if (*error) {
- (*error)->code = os->error_code;
- goto free_resources;
- }
-
- /* Then update the last_refreshed date of the podcast */
- touch_podcast (GRL_PODCASTS_SOURCE (os->source)->priv->db, os->media_id);
-
- /* If the feed contains no streams, notify and bail out */
- stream_count = xpathObj->nodesetval ? xpathObj->nodesetval->nodeNr : 0;
- GRL_DEBUG ("Got %d streams", stream_count);
-
- if (stream_count <= 0) {
- if (GRL_PODCASTS_SOURCE (os->source)->priv->notify_changes) {
- podcast = grl_media_box_new ();
- grl_media_set_id (podcast, os->media_id);
- grl_media_source_notify_change (os->source,
- podcast,
- GRL_CONTENT_CHANGED,
- FALSE);
- g_object_unref (podcast);
- }
- os->callback (os->source,
- os->operation_id,
- NULL,
- 0,
- os->user_data,
- NULL);
- goto free_resources;
- }
-
- /* Otherwise parse the streams in idle loop to prevent blocking */
- OperationSpecParse *osp = g_slice_new0 (OperationSpecParse);
- osp->os = os;
- osp->doc = doc;
- osp->xpathCtx = xpathCtx;
- osp->xpathObj = xpathObj;
- osp->parse_count = stream_count;
- g_idle_add (parse_entry_idle, osp);
- return;
-
- free_resources:
- if (xpathObj)
- xmlXPathFreeObject (xpathObj);
- if (xpathCtx)
- xmlXPathFreeContext (xpathCtx);
- if (doc)
- xmlFreeDoc (doc);
-}
-
-static void
-read_feed_cb (gchar *xmldata, gpointer user_data)
-{
- GError *error = NULL;
- OperationSpec *os = (OperationSpec *) user_data;
-
- if (!xmldata) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Failed to read data from podcast");
- } else {
- parse_feed (os, xmldata, &error);
- }
-
- if (error) {
- os->callback (os->source,
- os->operation_id,
- NULL,
- 0,
- os->user_data,
- error);
- g_error_free (error);
- g_slice_free (OperationSpec, os);
- }
-}
-
-static sqlite3_stmt *
-get_podcast_info (sqlite3 *db, const gchar *podcast_id)
-{
- gint r;
- sqlite3_stmt *sql_stmt = NULL;
- gchar *sql;
-
- GRL_DEBUG ("get_podcast_info");
-
- sql = g_strdup_printf (GRL_SQL_GET_PODCAST_BY_ID, podcast_id);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to retrieve podcast '%s': %s",
- podcast_id, sqlite3_errmsg (db));
- return NULL;
- }
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- if (r == SQLITE_ROW) {
- return sql_stmt;
- } else {
- GRL_WARNING ("Failed to retrieve podcast information: %s",
- sqlite3_errmsg (db));
- sqlite3_finalize (sql_stmt);
- return NULL;
- }
-}
-
-static void
-produce_podcast_contents (OperationSpec *os)
-{
- sqlite3_stmt *sql_stmt = NULL;
- sqlite3 *db;
- GError *error;
- gchar *url;
-
- GRL_DEBUG ("produce_podcast_contents");
-
- /* First we get some information about the podcast */
- db = GRL_PODCASTS_SOURCE (os->source)->priv->db;
- sql_stmt = get_podcast_info (db, os->media_id);
- if (sql_stmt) {
- gchar *lr_str;
- GTimeVal lr;
- GTimeVal now;
-
- /* Check if we have to refresh the podcast */
- lr_str = (gchar *) sqlite3_column_text (sql_stmt, PODCAST_LAST_REFRESHED);
- GRL_DEBUG ("Podcast last-refreshed: '%s'", lr_str);
- g_time_val_from_iso8601 (lr_str ? lr_str : "", &lr);
- g_get_current_time (&now);
- now.tv_sec -= CACHE_DURATION;
- if (now.tv_sec >= lr.tv_sec) {
- /* We have to read the podcast feed again */
- GRL_DEBUG ("Refreshing podcast '%s'...", os->media_id);
- url = g_strdup ((gchar *) sqlite3_column_text (sql_stmt, PODCAST_URL));
- read_url_async (GRL_PODCASTS_SOURCE (os->source), url, read_feed_cb, os);
- g_free (url);
- } else {
- /* We can read the podcast entries from the database cache */
- produce_podcast_contents_from_db (os);
- g_slice_free (OperationSpec, os);
- }
- sqlite3_finalize (sql_stmt);
- } else {
- error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to retrieve podcast information");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
- g_slice_free (OperationSpec, os);
- }
-
-}
-
-static void
-produce_podcasts (OperationSpec *os)
-{
- gint r;
- sqlite3_stmt *sql_stmt = NULL;
- sqlite3 *db;
- GrlMedia *media;
- GError *error = NULL;
- GList *medias = NULL;
- guint count = 0;
- GList *iter;
- gchar *sql;
-
- GRL_DEBUG ("produce_podcasts");
-
- db = GRL_PODCASTS_SOURCE (os->source)->priv->db;
-
- if (os->is_query) {
- /* Query */
- sql = g_strdup_printf (GRL_SQL_GET_PODCASTS_BY_QUERY,
- os->text, os->count, os->skip);
- } else {
- /* Browse */
- sql = g_strdup_printf (GRL_SQL_GET_PODCASTS, os->count, os->skip);
- }
- GRL_DEBUG ("%s", sql);
- r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to retrieve podcasts: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to retrieve podcasts list");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
- goto free_resources;
- }
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- while (r == SQLITE_ROW) {
- media = build_media_from_stmt (NULL, sql_stmt, TRUE);
- medias = g_list_prepend (medias, media);
- count++;
- r = sqlite3_step (sql_stmt);
- }
-
- if (r != SQLITE_DONE) {
- GRL_WARNING ("Failed to retrieve podcasts: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to retrieve podcasts list");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
- goto free_resources;
- }
-
- if (count > 0) {
- medias = g_list_reverse (medias);
- iter = medias;
- while (iter) {
- media = GRL_MEDIA (iter->data);
- os->callback (os->source,
- os->operation_id,
- media,
- --count,
- os->user_data,
- NULL);
- iter = g_list_next (iter);
- }
- g_list_free (medias);
- } else {
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
- }
-
- free_resources:
- if (sql_stmt)
- sqlite3_finalize (sql_stmt);
-}
-
-static void
-stream_metadata (GrlMediaSourceMetadataSpec *ms)
-{
- gint r;
- sqlite3_stmt *sql_stmt = NULL;
- sqlite3 *db;
- GError *error = NULL;
- gchar *sql;
- const gchar *id;
-
- GRL_DEBUG ("stream_metadata");
-
- db = GRL_PODCASTS_SOURCE (ms->source)->priv->db;
-
- id = grl_media_get_id (ms->media);
- sql = g_strdup_printf (GRL_SQL_GET_PODCAST_STREAM, id);
- GRL_DEBUG ("%s", sql);
- r = sqlite3_prepare_v2 (db, sql, strlen (sql), &sql_stmt, NULL);
- g_free (sql);
-
- if (r != SQLITE_OK) {
- GRL_WARNING ("Failed to get podcast stream: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Failed to get podcast stream metadata");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- return;
- }
-
- while ((r = sqlite3_step (sql_stmt)) == SQLITE_BUSY);
-
- if (r == SQLITE_ROW) {
- build_media_from_stmt (ms->media, sql_stmt, FALSE);
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- } else {
- GRL_WARNING ("Failed to get podcast stream: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Failed to get podcast stream metadata");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- }
-
- sqlite3_finalize (sql_stmt);
-}
-
-static void
-podcast_metadata (GrlMediaSourceMetadataSpec *ms)
-{
- sqlite3_stmt *sql_stmt = NULL;
- sqlite3 *db;
- GError *error = NULL;
- const gchar *id;
-
- GRL_DEBUG ("podcast_metadata");
-
- db = GRL_PODCASTS_SOURCE (ms->source)->priv->db;
-
- id = grl_media_get_id (ms->media);
- if (!id) {
- /* Root category: special case */
- grl_media_set_title (ms->media, GRL_ROOT_TITLE);
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- return;
- }
-
- sql_stmt = get_podcast_info (db, id);
-
- if (sql_stmt) {
- build_media_from_stmt (ms->media, sql_stmt, TRUE);
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- sqlite3_finalize (sql_stmt);
- } else {
- GRL_WARNING ("Failed to get podcast: %s", sqlite3_errmsg (db));
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Failed to get podcast metadata");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- }
-}
-
-static gboolean
-media_id_is_podcast (const gchar *id)
-{
- return g_ascii_strtoll (id, NULL, 10) != 0;
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_podcasts_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_URL,
- GRL_METADATA_KEY_CHILDCOUNT,
- GRL_METADATA_KEY_SITE,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_podcasts_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- GRL_DEBUG ("grl_podcasts_source_browse");
-
- OperationSpec *os;
- GrlPodcastsSource *podcasts_source;
- GError *error = NULL;
-
- podcasts_source = GRL_PODCASTS_SOURCE (source);
- if (!podcasts_source->priv->db) {
- GRL_WARNING ("Can't execute operation: no database connection.");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "No database connection");
- bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, error);
- g_error_free (error);
- return;
- }
-
- /* Configure browse operation */
- os = g_slice_new0 (OperationSpec);
- os->source = bs->source;
- os->operation_id = bs->browse_id;
- os->media_id = grl_media_get_id (bs->container);
- os->count = bs->count;
- os->skip = bs->skip;
- os->callback = bs->callback;
- os->user_data = bs->user_data;
- os->error_code = GRL_CORE_ERROR_BROWSE_FAILED;
-
- if (!os->media_id) {
- /* Browsing podcasts list */
- produce_podcasts (os);
- g_slice_free (OperationSpec, os);
- } else {
- /* Browsing a particular podcast. We may need to parse
- the feed (async) and in that case we will need to keep
- os, so we do not free os here */
- produce_podcast_contents (os);
- }
-}
-
-static void
-grl_podcasts_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss)
-{
- GRL_DEBUG ("grl_podcasts_source_search");
-
- GrlPodcastsSource *podcasts_source;
- OperationSpec *os;
- GError *error = NULL;
-
- podcasts_source = GRL_PODCASTS_SOURCE (source);
- if (!podcasts_source->priv->db) {
- GRL_WARNING ("Can't execute operation: no database connection.");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_QUERY_FAILED,
- "No database connection");
- ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, error);
- g_error_free (error);
- return;
- }
-
- os = g_slice_new0 (OperationSpec);
- os->source = ss->source;
- os->operation_id = ss->search_id;
- os->text = ss->text;
- os->count = ss->count;
- os->skip = ss->skip;
- os->callback = ss->callback;
- os->user_data = ss->user_data;
- os->is_query = TRUE;
- os->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
- produce_podcast_contents_from_db (os);
- g_slice_free (OperationSpec, os);
-}
-
-static void
-grl_podcasts_source_query (GrlMediaSource *source, GrlMediaSourceQuerySpec *qs)
-{
- GRL_DEBUG ("grl_podcasts_source_query");
-
- GrlPodcastsSource *podcasts_source;
- OperationSpec *os;
- GError *error = NULL;
-
- podcasts_source = GRL_PODCASTS_SOURCE (source);
- if (!podcasts_source->priv->db) {
- GRL_WARNING ("Can't execute operation: no database connection.");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_QUERY_FAILED,
- "No database connection");
- qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
- g_error_free (error);
- return;
- }
-
- os = g_slice_new0 (OperationSpec);
- os->source = qs->source;
- os->operation_id = qs->query_id;
- os->text = qs->query;
- os->count = qs->count;
- os->skip = qs->skip;
- os->callback = qs->callback;
- os->user_data = qs->user_data;
- os->is_query = TRUE;
- os->error_code = GRL_CORE_ERROR_QUERY_FAILED;
- produce_podcasts (os);
- g_slice_free (OperationSpec, os);
-}
-
-static void
-grl_podcasts_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- GRL_DEBUG ("grl_podcasts_source_metadata");
-
- GrlPodcastsSource *podcasts_source;
- GError *error = NULL;
- const gchar *media_id;
-
- podcasts_source = GRL_PODCASTS_SOURCE (source);
- if (!podcasts_source->priv->db) {
- GRL_WARNING ("Can't execute operation: no database connection.");
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "No database connection");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- return;
- }
-
- media_id = grl_media_get_id (ms->media);
- if (!media_id || media_id_is_podcast (media_id)) {
- podcast_metadata (ms);
- } else {
- stream_metadata (ms);
- }
-}
-
-static void
-grl_podcasts_source_store (GrlMediaSource *source, GrlMediaSourceStoreSpec *ss)
-{
- GRL_DEBUG ("grl_podcasts_source_store");
- GError *error = NULL;
- if (GRL_IS_MEDIA_BOX (ss->media)) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_STORE_FAILED,
- "Cannot create containers. Only feeds are accepted.");
- } else {
- store_podcast (GRL_PODCASTS_SOURCE (ss->source),
- ss->media,
- &error);
- }
- ss->callback (ss->source, ss->parent, ss->media, ss->user_data, error);
- if (error) {
- g_error_free (error);
- }
-}
-
-static void
-grl_podcasts_source_remove (GrlMediaSource *source,
- GrlMediaSourceRemoveSpec *rs)
-{
- GRL_DEBUG ("grl_podcasts_source_remove");
- GError *error = NULL;
- if (media_id_is_podcast (rs->media_id)) {
- remove_podcast (GRL_PODCASTS_SOURCE (rs->source),
- rs->media_id, &error);
- } else {
- remove_stream (GRL_PODCASTS_SOURCE (rs->source),
- rs->media_id, &error);
- }
- rs->callback (rs->source, rs->media, rs->user_data, error);
- if (error) {
- g_error_free (error);
- }
-}
-
-static gboolean
-grl_podcasts_source_notify_change_start (GrlMediaSource *source,
- GError **error)
-{
- GrlPodcastsSource *podcasts_source = GRL_PODCASTS_SOURCE (source);
-
- podcasts_source->priv->notify_changes = TRUE;
-
- return TRUE;
-}
-
-static gboolean
-grl_podcasts_source_notify_change_stop (GrlMediaSource *source,
- GError **error)
-{
- GrlPodcastsSource *podcasts_source = GRL_PODCASTS_SOURCE (source);
-
- podcasts_source->priv->notify_changes = FALSE;
-
- return TRUE;
-}
diff --git a/src/podcasts/grl-podcasts.h b/src/podcasts/grl-podcasts.h
deleted file mode 100644
index 039ba64..0000000
--- a/src/podcasts/grl-podcasts.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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_PODCASTS_SOURCE_H_
-#define _GRL_PODCASTS_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_PODCASTS_SOURCE_TYPE \
- (grl_podcasts_source_get_type ())
-
-#define GRL_PODCASTS_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_PODCASTS_SOURCE_TYPE, \
- GrlPodcastsSource))
-
-#define GRL_IS_PODCASTS_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_PODCASTS_SOURCE_TYPE))
-
-#define GRL_PODCASTS_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_PODCASTS_SOURCE_TYPE, \
- GrlPodcastsSourceClass))
-
-#define GRL_IS_PODCASTS_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_PODCASTS_SOURCE_TYPE))
-
-#define GRL_PODCASTS_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_PODCASTS_SOURCE_TYPE, \
- GrlPodcastsSourceClass))
-
-typedef struct _GrlPodcastsPrivate GrlPodcastsPrivate;
-typedef struct _GrlPodcastsSource GrlPodcastsSource;
-
-struct _GrlPodcastsSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlPodcastsPrivate *priv;
-};
-
-typedef struct _GrlPodcastsSourceClass GrlPodcastsSourceClass;
-
-struct _GrlPodcastsSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_podcasts_source_get_type (void);
-
-#endif /* _GRL_PODCASTS_SOURCE_H_ */
diff --git a/src/podcasts/grl-podcasts.xml b/src/podcasts/grl-podcasts.xml
deleted file mode 100644
index 1222f5c..0000000
--- a/src/podcasts/grl-podcasts.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Podcasts</name>
- <description>A plugin for browsing podcasts</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/shoutcast/Makefile.am b/src/shoutcast/Makefile.am
deleted file mode 100644
index 3940692..0000000
--- a/src/shoutcast/Makefile.am
+++ /dev/null
@@ -1,37 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Juan A. Suarez Romero <jasuarez igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlshoutcast.la
-
-libgrlshoutcast_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GRLNET_CFLAGS) \
- $(XML_CFLAGS)
-
-libgrlshoutcast_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GRLNET_LIBS) \
- $(XML_LIBS)
-
-libgrlshoutcast_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlshoutcast_la_SOURCES = grl-shoutcast.c grl-shoutcast.h
-
-libdir=$(GRL_PLUGINS_DIR)
-
-shoutcastxmldir = $(GRL_PLUGINS_CONF_DIR)
-shoutcastxml_DATA = $(SHOUTCAST_PLUGIN_ID).xml
-
-EXTRA_DIST = $(shoutcastxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/shoutcast/grl-shoutcast.c b/src/shoutcast/grl-shoutcast.c
deleted file mode 100644
index 6b08928..0000000
--- a/src/shoutcast/grl-shoutcast.c
+++ /dev/null
@@ -1,727 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
-#include <net/grl-net.h>
-#include <libxml/parser.h>
-#include <libxml/xpath.h>
-
-#include "grl-shoutcast.h"
-
-#define EXPIRE_CACHE_TIMEOUT 300
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT shoutcast_log_domain
-GRL_LOG_DOMAIN_STATIC(shoutcast_log_domain);
-
-/* ------ SHOUTcast API ------ */
-
-#define SHOUTCAST_BASE_ENTRY "http://yp.shoutcast.com"
-
-#define SHOUTCAST_GET_GENRES SHOUTCAST_BASE_ENTRY "/sbin/newxml.phtml"
-#define SHOUTCAST_GET_RADIOS SHOUTCAST_GET_GENRES "?genre=%s&limit=%u"
-#define SHOUTCAST_SEARCH_RADIOS SHOUTCAST_GET_GENRES "?search=%s&limit=%u"
-#define SHOUTCAST_TUNE SHOUTCAST_BASE_ENTRY "/sbin/tunein-station.pls?id=%s"
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID SHOUTCAST_PLUGIN_ID
-
-#define SOURCE_ID "grl-shoutcast"
-#define SOURCE_NAME "SHOUTcast"
-#define SOURCE_DESC "A source for browsing SHOUTcast radios"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-typedef struct {
- GrlMedia *media;
- GrlMediaSource *source;
- GrlMediaSourceMetadataCb metadata_cb;
- GrlMediaSourceResultCb result_cb;
- gboolean cancelled;
- gboolean cache;
- gchar *filter_entry;
- gchar *genre;
- gint error_code;
- gint operation_id;
- gint to_send;
- gpointer user_data;
- guint count;
- guint skip;
- xmlDocPtr xml_doc;
- xmlNodePtr xml_entries;
-} OperationData;
-
-static GrlNetWc *wc = NULL;
-static GCancellable *cancellable;
-static gchar *cached_page = NULL;
-static gboolean cached_page_expired = TRUE;
-
-static GrlShoutcastSource *grl_shoutcast_source_new (void);
-
-gboolean grl_shoutcast_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-static const GList *grl_shoutcast_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_shoutcast_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms);
-
-static void grl_shoutcast_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-
-static void grl_shoutcast_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-
-static void grl_shoutcast_source_cancel (GrlMediaSource *source,
- guint operation_id);
-
-static void read_url_async (const gchar *url, OperationData *op_data);
-
-static void grl_shoutcast_source_finalize (GObject *object);
-
-/* =================== SHOUTcast Plugin =============== */
-
-gboolean
-grl_shoutcast_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (shoutcast_log_domain, "shoutcast");
-
- GRL_DEBUG ("shoutcast_plugin_init");
-
- GrlShoutcastSource *source = grl_shoutcast_source_new ();
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_shoutcast_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== SHOUTcast GObject ================ */
-
-static GrlShoutcastSource *
-grl_shoutcast_source_new (void)
-{
- GRL_DEBUG ("grl_shoutcast_source_new");
- return g_object_new (GRL_SHOUTCAST_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_shoutcast_source_class_init (GrlShoutcastSourceClass * klass)
-{
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- source_class->metadata = grl_shoutcast_source_metadata;
- source_class->browse = grl_shoutcast_source_browse;
- source_class->search = grl_shoutcast_source_search;
- source_class->cancel = grl_shoutcast_source_cancel;
- metadata_class->supported_keys = grl_shoutcast_source_supported_keys;
- gobject_class->finalize = grl_shoutcast_source_finalize;
-}
-
-static void
-grl_shoutcast_source_init (GrlShoutcastSource *source)
-{
-}
-
-G_DEFINE_TYPE (GrlShoutcastSource, grl_shoutcast_source, GRL_TYPE_MEDIA_SOURCE);
-
-static void
-grl_shoutcast_source_finalize (GObject *object)
-{
- if (wc && GRL_IS_NET_WC (wc))
- g_object_unref (wc);
-
- if (cancellable && G_IS_CANCELLABLE (cancellable))
- g_cancellable_cancel (cancellable);
-
- G_OBJECT_CLASS (grl_shoutcast_source_parent_class)->finalize (object);
-}
-
-/* ======================= Private ==================== */
-
-static gint
-xml_count_nodes (xmlNodePtr node)
-{
- gint count = 0;
-
- while (node) {
- count++;
- node = node->next;
- }
-
- return count;
-}
-
-static GrlMedia *
-build_media_from_genre (OperationData *op_data)
-{
- GrlMedia *media;
- gchar *genre_name;
-
- if (op_data->media) {
- media = op_data->media;
- } else {
- media = grl_media_box_new ();
- }
-
- genre_name = (gchar *) xmlGetProp (op_data->xml_entries,
- (const xmlChar *) "name");
-
- grl_media_set_id (media, genre_name);
- grl_media_set_title (media, genre_name);
- grl_data_set_string (GRL_DATA (media),
- GRL_METADATA_KEY_GENRE,
- genre_name);
- g_free (genre_name);
-
- return media;
-}
-
-static GrlMedia *
-build_media_from_station (OperationData *op_data)
-{
- GrlMedia *media;
- gchar **station_genres = NULL;
- gchar *media_id;
- gchar *media_url;
- gchar *station_bitrate;
- gchar *station_genre;
- gchar *station_genre_field;
- gchar *station_id;
- gchar *station_mime;
- gchar *station_name;
-
- station_name = (gchar *) xmlGetProp (op_data->xml_entries,
- (const xmlChar *) "name");
- station_mime = (gchar *) xmlGetProp (op_data->xml_entries,
- (const xmlChar *) "mt");
- station_id = (gchar *) xmlGetProp (op_data->xml_entries,
- (const xmlChar *) "id");
- station_bitrate = (gchar *) xmlGetProp (op_data->xml_entries,
- (const xmlChar *) "br");
- if (op_data->media) {
- media = op_data->media;
- } else {
- media = grl_media_audio_new ();
- }
-
- if (op_data->genre) {
- station_genre = op_data->genre;
- } else {
- station_genre_field = (gchar *) xmlGetProp (op_data->xml_entries,
- (const xmlChar *) "genre");
- station_genres = g_strsplit (station_genre_field, " ", -1);
- g_free (station_genre_field);
- station_genre = station_genres[0];
- }
-
- media_id = g_strconcat (station_genre, "/", station_id, NULL);
- media_url = g_strdup_printf (SHOUTCAST_TUNE, station_id);
-
- grl_media_set_id (media, media_id);
- grl_media_set_title (media, station_name);
- grl_media_set_mime (media, station_mime);
- grl_media_audio_set_genre (GRL_MEDIA_AUDIO (media), station_genre);
- grl_media_set_url (media, media_url);
- grl_media_audio_set_bitrate (GRL_MEDIA_AUDIO (media),
- atoi (station_bitrate));
-
- g_free (station_name);
- g_free (station_mime);
- g_free (station_id);
- g_free (station_bitrate);
- g_free (media_id);
- g_free (media_url);
- if (station_genres) {
- g_strfreev (station_genres);
- }
-
- return media;
-}
-
-static gboolean
-send_media (OperationData *op_data, GrlMedia *media)
-{
- if (!op_data->cancelled) {
- op_data->result_cb (op_data->source,
- op_data->operation_id,
- media,
- --op_data->to_send,
- op_data->user_data,
- NULL);
-
- op_data->xml_entries = op_data->xml_entries->next;
- } else {
- op_data->result_cb (op_data->source,
- op_data->operation_id,
- NULL,
- 0,
- op_data->user_data,
- NULL);
- }
-
- if (op_data->to_send == 0 || op_data->cancelled) {
- xmlFreeDoc (op_data->xml_doc);
- g_slice_free (OperationData, op_data);
- return FALSE;
- } else {
- return TRUE;
- }
-}
-
-static gboolean
-send_genrelist_entries (OperationData *op_data)
-{
- return send_media (op_data,
- build_media_from_genre (op_data));
-}
-
-static gboolean
-send_stationlist_entries (OperationData *op_data)
-{
- return send_media (op_data,
- build_media_from_station (op_data));
-}
-
-static void
-xml_parse_result (const gchar *str, OperationData *op_data)
-{
- GError *error = NULL;
- gboolean stationlist_result;
- gchar *xpath_expression;
- xmlNodePtr node;
- xmlXPathContextPtr xpath_ctx;
- xmlXPathObjectPtr xpath_res;
-
- if (op_data->cancelled) {
- op_data->result_cb (op_data->source,
- op_data->operation_id,
- NULL,
- 0,
- op_data->user_data,
- NULL);
- g_slice_free (OperationData, op_data);
- return;
- }
-
- op_data->xml_doc = xmlReadMemory (str, xmlStrlen ((xmlChar*) str), NULL, NULL,
- XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- if (!op_data->xml_doc) {
- error = g_error_new (GRL_CORE_ERROR,
- op_data->error_code,
- "Failed to parse SHOUTcast's response");
- goto finalize;
- }
-
- node = xmlDocGetRootElement (op_data->xml_doc);
- if (!node) {
- error = g_error_new (GRL_CORE_ERROR,
- op_data->error_code,
- "Empty response from SHOUTcast");
- goto finalize;
- }
-
- stationlist_result = (xmlStrcmp (node->name,
- (const xmlChar *) "stationlist") == 0);
-
- op_data->xml_entries = node->xmlChildrenNode;
-
- /* Check if we are interesting only in updating a media (that is, a metadata()
- operation) or just browsing/searching */
- if (op_data->media) {
-
- /* Search for node */
- xpath_ctx = xmlXPathNewContext (op_data->xml_doc);
- if (xpath_ctx) {
- if (stationlist_result) {
- xpath_expression = g_strdup_printf ("//station[@id = \"%s\"]",
- op_data->filter_entry);
- } else {
- xpath_expression = g_strdup_printf ("//genre[@name = \"%s\"]",
- op_data->filter_entry);
- }
- xpath_res = xmlXPathEvalExpression ((xmlChar *) xpath_expression,
- xpath_ctx);
- g_free (xpath_expression);
-
- if (xpath_res && xpath_res->nodesetval->nodeTab &&
- xpath_res->nodesetval->nodeTab[0]) {
- op_data->xml_entries = xpath_res->nodesetval->nodeTab[0];
- if (stationlist_result) {
- build_media_from_station (op_data);
- } else {
- build_media_from_genre (op_data);
- }
- } else {
- error = g_error_new (GRL_CORE_ERROR,
- op_data->error_code,
- "Can not find media '%s'",
- grl_media_get_id (op_data->media));
- }
- if (xpath_res) {
- xmlXPathFreeObject (xpath_res);
- }
- xmlXPathFreeContext (xpath_ctx);
- } else {
- error = g_error_new (GRL_CORE_ERROR,
- op_data->error_code,
- "Can not build xpath context");
- }
-
- op_data->metadata_cb (
- op_data->source,
- op_data->media,
- op_data->user_data,
- error);
- goto free_resources;
- }
-
- if (stationlist_result) {
- /* First node is "tunein"; skip it */
- op_data->xml_entries = op_data->xml_entries->next;
- }
-
- /* Skip elements */
- while (op_data->xml_entries && op_data->skip > 0) {
- op_data->xml_entries = op_data->xml_entries->next;
- op_data->skip--;
- }
-
- /* Check if there are elements to send*/
- if (!op_data->xml_entries || op_data->count == 0) {
- goto finalize;
- }
-
- /* Compute how many items are to be sent */
- op_data->to_send = xml_count_nodes (op_data->xml_entries);
- if (op_data->to_send > op_data->count) {
- op_data->to_send = op_data->count;
- }
-
- if (stationlist_result) {
- g_idle_add ((GSourceFunc) send_stationlist_entries, op_data);
- } else {
- g_idle_add ((GSourceFunc) send_genrelist_entries, op_data);
- }
-
- return;
-
- finalize:
- op_data->result_cb (op_data->source,
- op_data->operation_id,
- NULL,
- 0,
- op_data->user_data,
- error);
-
- free_resources:
- if (op_data->xml_doc) {
- xmlFreeDoc (op_data->xml_doc);
- }
-
- if (op_data->filter_entry) {
- g_free (op_data->filter_entry);
- }
-
- if (error) {
- g_error_free (error);
- }
- g_slice_free (OperationData, op_data);
-}
-
-static gboolean
-expire_cache (gpointer user_data)
-{
- GRL_DEBUG ("Cached page expired");
- cached_page_expired = TRUE;
- return FALSE;
-}
-
-static void
-read_done_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- GError *error = NULL;
- GError *wc_error = NULL;
- OperationData *op_data = (OperationData *) user_data;
- gboolean cache;
- gchar *content = NULL;
-
- if (!grl_net_wc_request_finish (GRL_NET_WC (source_object),
- res,
- &content,
- NULL,
- &wc_error)) {
- error = g_error_new (GRL_CORE_ERROR,
- op_data->error_code,
- "Failed to connect SHOUTcast: '%s'",
- wc_error->message);
- op_data->result_cb (op_data->source,
- op_data->operation_id,
- NULL,
- 0,
- op_data->user_data,
- error);
- g_error_free (wc_error);
- g_error_free (error);
- g_slice_free (OperationData, op_data);
-
- return;
- }
-
- cache = op_data->cache;
- xml_parse_result (content, op_data);
- if (cache && cached_page_expired) {
- GRL_DEBUG ("Caching page");
- g_free (cached_page);
- cached_page = g_strdup (content);
- cached_page_expired = FALSE;
- g_timeout_add_seconds (EXPIRE_CACHE_TIMEOUT, expire_cache, NULL);
- }
-}
-
-static gboolean
-read_cached_page (OperationData *op_data)
-{
- xml_parse_result (cached_page, op_data);
- return FALSE;
-}
-
-static void
-read_url_async (const gchar *url, OperationData *op_data)
-{
- if (op_data->cache && !cached_page_expired) {
- GRL_DEBUG ("Using cached page");
- g_idle_add ((GSourceFunc) read_cached_page, op_data);
- } else {
- if (!wc)
- wc = grl_net_wc_new ();
-
- cancellable = g_cancellable_new ();
- grl_net_wc_request_async (wc, url, cancellable, read_done_cb, op_data);
- }
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_shoutcast_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_BITRATE,
- GRL_METADATA_KEY_GENRE,
- GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_MIME,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_URL,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_shoutcast_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- const gchar *media_id;
- gchar **id_tokens;
- gchar *url = NULL;
- OperationData *data = NULL;
-
- /* Unfortunately, shoutcast does not have an API to get information about a
- station. Thus, steps done to obtain the Content must be repeated. For
- instance, if we have a Media with id "Pop/1321", it means that it is
- station #1321 that was obtained after browsing "Pop" category. Thus we have
- repeat the Pop browsing and get the result with station id 1321. If it
- doesn't exist (think in results obtained from a search), we do nothing */
-
- media_id = grl_media_get_id (ms->media);
-
- /* Check if we need to report about root category */
- if (!media_id) {
- grl_media_set_title (ms->media, "SHOUTcast");
- } else {
- data = g_slice_new0 (OperationData);
- data->source = source;
- data->count = 1;
- data->metadata_cb = ms->callback;
- data->user_data = ms->user_data;
- data->error_code = GRL_CORE_ERROR_METADATA_FAILED;
- data->media = ms->media;
-
- id_tokens = g_strsplit (media_id, "/", -1);
-
- /* Check if Content is a media */
- if (id_tokens[1]) {
- data->filter_entry = g_strdup (id_tokens[1]);
-
- /* Check if result is from a previous search */
- if (id_tokens[0][0] == '?') {
- url = g_strdup_printf (SHOUTCAST_SEARCH_RADIOS,
- id_tokens[0]+1,
- G_MAXINT);
- } else {
- url = g_strdup_printf (SHOUTCAST_GET_RADIOS,
- id_tokens[0],
- G_MAXINT);
- }
- } else {
- data->filter_entry = g_strdup (id_tokens[0]);
- data->cache = TRUE;
- url = g_strdup (SHOUTCAST_GET_GENRES);
- }
-
- g_strfreev (id_tokens);
- }
-
- if (url) {
- read_url_async (url, data);
- g_free (url);
- } else {
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- }
-}
-
-static void
-grl_shoutcast_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- OperationData *data;
- const gchar *container_id;
- gchar *url;
-
- GRL_DEBUG ("grl_shoutcast_source_browse");
-
- data = g_slice_new0 (OperationData);
- data->source = source;
- data->operation_id = bs->browse_id;
- data->result_cb = bs->callback;
- data->skip = bs->skip;
- data->count = bs->count;
- data->user_data = bs->user_data;
- data->error_code = GRL_CORE_ERROR_BROWSE_FAILED;
-
- container_id = grl_media_get_id (bs->container);
-
- /* If it's root category send list of genres; else send list of radios */
- if (!container_id) {
- data->cache = TRUE;
- url = g_strdup (SHOUTCAST_GET_GENRES);
- } else {
- url = g_strdup_printf (SHOUTCAST_GET_RADIOS,
- container_id,
- bs->skip + bs->count);
- data->genre = g_strdup (container_id);
- }
-
- grl_media_source_set_operation_data (source, bs->browse_id, data);
-
- read_url_async (url, data);
-
- g_free (url);
-}
-
-static void
-grl_shoutcast_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss)
-{
- GError *error;
- OperationData *data;
- gchar *url;
-
- /* Check if there is text to search */
- if (!ss->text || ss->text[0] == '\0') {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_SEARCH_FAILED,
- "Search text not specified");
- ss->callback (ss->source,
- ss->search_id,
- NULL,
- 0,
- ss->user_data,
- error);
-
- g_error_free (error);
- return;
- }
-
- data = g_slice_new0 (OperationData);
- data->source = source;
- data->operation_id = ss->search_id;
- data->result_cb = ss->callback;
- data->skip = ss->skip;
- data->count = ss->count;
- data->user_data = ss->user_data;
- data->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
-
- grl_media_source_set_operation_data (source, ss->search_id, data);
-
- url = g_strdup_printf (SHOUTCAST_SEARCH_RADIOS,
- ss->text,
- ss->skip + ss->count);
-
- read_url_async (url, data);
-
- g_free (url);
-}
-
-static void
-grl_shoutcast_source_cancel (GrlMediaSource *source, guint operation_id)
-{
- OperationData *op_data;
-
- GRL_DEBUG ("grl_shoutcast_source_cancel");
-
- if (cancellable && G_IS_CANCELLABLE (cancellable))
- g_cancellable_cancel (cancellable);
- cancellable = NULL;
-
- op_data = (OperationData *) grl_media_source_get_operation_data (source, operation_id);
-
- if (op_data) {
- op_data->cancelled = TRUE;
- }
-}
diff --git a/src/shoutcast/grl-shoutcast.h b/src/shoutcast/grl-shoutcast.h
deleted file mode 100644
index 741fac0..0000000
--- a/src/shoutcast/grl-shoutcast.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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_SHOUTCAST_SOURCE_H_
-#define _GRL_SHOUTCAST_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_SHOUTCAST_SOURCE_TYPE \
- (grl_shoutcast_source_get_type ())
-
-#define GRL_SHOUTCAST_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_SHOUTCAST_SOURCE_TYPE, \
- GrlShoutcastSource))
-
-#define GRL_IS_SHOUTCAST_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_SHOUTCAST_SOURCE_TYPE))
-
-#define GRL_SHOUTCAST_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_SHOUTCAST_SOURCE_TYPE, \
- GrlShoutcastSourceClass))
-
-#define GRL_IS_SHOUTCAST_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_SHOUTCAST_SOURCE_TYPE))
-
-#define GRL_SHOUTCAST_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_SHOUTCAST_SOURCE_TYPE, \
- GrlShoutcastSourceClass))
-
-typedef struct _GrlShoutcastSource GrlShoutcastSource;
-
-struct _GrlShoutcastSource {
-
- GrlMediaSource parent;
-
-};
-
-typedef struct _GrlShoutcastSourceClass GrlShoutcastSourceClass;
-
-struct _GrlShoutcastSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_shoutcast_source_get_type (void);
-
-#endif /* _GRL_SHOUTCAST_SOURCE_H_ */
diff --git a/src/shoutcast/grl-shoutcast.xml b/src/shoutcast/grl-shoutcast.xml
deleted file mode 100644
index 2f9e916..0000000
--- a/src/shoutcast/grl-shoutcast.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>SHOUTcast</name>
- <description>A plugin for browsing SHOUTcast radios</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/tracker/Makefile.am b/src/tracker/Makefile.am
deleted file mode 100644
index 5510f5a..0000000
--- a/src/tracker/Makefile.am
+++ /dev/null
@@ -1,36 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Juan A. Suarez Romero <jasuarez igalia com>
-#
-# Copyright (C) 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrltracker.la
-
-libgrltracker_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(TRACKER_SPARQL_CFLAGS)
-
-libgrltracker_la_LIBADD = \
- $(DEPS_LIBS) \
- $(TRACKER_SPARQL_LIBS)
-
-libgrltracker_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrltracker_la_SOURCES = \
- grl-tracker.c \
- grl-tracker.h
-
-libdir = $(GRL_PLUGINS_DIR)
-trackerxmldir = $(GRL_PLUGINS_CONF_DIR)
-trackerxml_DATA = $(TRACKER_PLUGIN_ID).xml
-
-EXTRA_DIST = $(trackerxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/tracker/grl-tracker.c b/src/tracker/grl-tracker.c
deleted file mode 100644
index b07627f..0000000
--- a/src/tracker/grl-tracker.c
+++ /dev/null
@@ -1,1511 +0,0 @@
-/*
- * Copyright (C) 2011 Igalia S.L.
- * Copyright (C) 2011 Intel Corporation.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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 <grilo.h>
-#include <string.h>
-#include <gio/gio.h>
-#include <tracker-sparql.h>
-
-#include "grl-tracker.h"
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT tracker_log_domain
-GRL_LOG_DOMAIN_STATIC(tracker_log_domain);
-
-/* ------- Definitions ------- */
-
-#define MEDIA_TYPE "grilo-media-type"
-
-#define RDF_TYPE_ALBUM "nmm#MusicAlbum"
-#define RDF_TYPE_ARTIST "nmm#Artist"
-#define RDF_TYPE_AUDIO "nfo#Audio"
-#define RDF_TYPE_MUSIC "nmm#MusicPiece"
-#define RDF_TYPE_IMAGE "nmm#Photo"
-#define RDF_TYPE_VIDEO "nmm#Video"
-#define RDF_TYPE_BOX "grilo#Box"
-
-/* ---- Plugin information --- */
-
-#define PLUGIN_ID TRACKER_PLUGIN_ID
-
-#define SOURCE_ID "grl-tracker"
-#define SOURCE_NAME "Tracker"
-#define SOURCE_DESC "A plugin for searching multimedia content using Tracker"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-enum {
- METADATA,
- BROWSE,
- QUERY,
- SEARCH
-};
-
-/* --- Other --- */
-
-#define TRACKER_MOUNTED_DATASOURCES_START \
- "SELECT nie:dataSource(?urn) AS ?datasource " \
- "(SELECT nie:url(tracker:mountPoint(?ds)) " \
- "WHERE { ?urn nie:dataSource ?ds }) " \
- "(SELECT GROUP_CONCAT(tracker:isMounted(?ds), \",\") " \
- "WHERE { ?urn nie:dataSource ?ds }) " \
- "WHERE { ?urn a nfo:FileDataObject . FILTER (?urn IN ("
-
-#define TRACKER_MOUNTED_DATASOURCES_END " ))} GROUP BY (?datasource)"
-
-#define TRACKER_DATASOURCES_REQUEST \
- "SELECT ?urn nie:dataSource(?urn) AS ?source " \
- "(SELECT GROUP_CONCAT(nie:url(tracker:mountPoint(?ds)), \",\") " \
- "WHERE { ?urn nie:dataSource ?ds }) " \
- "WHERE { " \
- "?urn tracker:available ?tr . " \
- "?source a tracker:Volume . " \
- "FILTER (bound(nie:dataSource(?urn))) " \
- "} " \
- "GROUP BY (?source)"
-
-#define TRACKER_QUERY_REQUEST \
- "SELECT rdf:type(?urn) %s " \
- "WHERE { %s . %s } " \
- "ORDER BY DESC(nfo:fileLastModified(?urn)) " \
- "OFFSET %i " \
- "LIMIT %i"
-
-#define TRACKER_SEARCH_REQUEST \
- "SELECT rdf:type(?urn) %s " \
- "WHERE " \
- "{ " \
- "?urn a nfo:Media . " \
- "?urn tracker:available ?tr . " \
- "?urn fts:match '%s' . " \
- "%s " \
- "} " \
- "ORDER BY DESC(nfo:fileLastModified(?urn)) " \
- "OFFSET %i " \
- "LIMIT %i"
-
-#define TRACKER_SEARCH_ALL_REQUEST \
- "SELECT rdf:type(?urn) %s " \
- "WHERE " \
- "{ " \
- "?urn a nfo:Media . " \
- "?urn tracker:available ?tr . " \
- "%s " \
- "} " \
- "ORDER BY DESC(nfo:fileLastModified(?urn)) " \
- "OFFSET %i " \
- "LIMIT %i"
-
-#define TRACKER_BROWSE_CATEGORY_REQUEST \
- "SELECT rdf:type(?urn) %s " \
- "WHERE " \
- "{ " \
- "?urn a %s . " \
- "?urn tracker:available ?tr . " \
- "%s " \
- "} " \
- "ORDER BY DESC(nfo:fileLastModified(?urn)) " \
- "OFFSET %i " \
- "LIMIT %i"
-
-#define TRACKER_METADATA_REQUEST \
- "SELECT %s " \
- "WHERE { ?urn a nie:DataObject . FILTER (?urn = <%s>) }"
-
-typedef struct {
- GrlKeyID grl_key;
- const gchar *sparql_key_name;
- const gchar *sparql_key_attr;
- const gchar *sparql_key_flavor;
-} tracker_grl_sparql_t;
-
-typedef struct {
- gboolean in_use;
-
- GHashTable *updated_items;
- GList *updated_items_list;
- GList *updated_items_iter;
-
- /* GList *updated_sources; */
-
- TrackerSparqlCursor *cursor;
-} tracker_evt_update_t;
-
-struct OperationSpec {
- GrlMediaSource *source;
- GrlTrackerSourcePriv *priv;
- guint operation_id;
- GCancellable *cancel_op;
- const GList *keys;
- guint skip;
- guint count;
- guint current;
- GrlMediaSourceResultCb callback;
- gpointer user_data;
- TrackerSparqlCursor *cursor;
-};
-
-enum {
- PROP_0,
- PROP_TRACKER_CONNECTION,
-};
-
-struct _GrlTrackerSourcePriv {
- TrackerSparqlConnection *tracker_connection;
-
- GHashTable *operations;
-
- gchar *tracker_datasource;
-};
-
-#define GRL_TRACKER_SOURCE_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_TRACKER_SOURCE_TYPE, \
- GrlTrackerSourcePriv))
-
-static GrlTrackerSource *grl_tracker_source_new (TrackerSparqlConnection *connection);
-
-static void grl_tracker_source_set_property (GObject *object,
- guint propid,
- const GValue *value,
- GParamSpec *pspec);
-
-static void grl_tracker_source_constructed (GObject *object);
-
-static void grl_tracker_source_finalize (GObject *object);
-
-gboolean grl_tracker_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-static const GList *grl_tracker_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_tracker_source_query (GrlMediaSource *source,
- GrlMediaSourceQuerySpec *qs);
-
-static void grl_tracker_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms);
-
-static void grl_tracker_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-
-static void grl_tracker_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-
-static void grl_tracker_source_cancel (GrlMediaSource *source,
- guint operation_id);
-
-static gchar *get_tracker_source_name (const gchar *uri,
- const gchar *datasource);
-
-static void setup_key_mappings (void);
-
-/* ===================== Globals ================= */
-
-static GHashTable *grl_to_sparql_mapping = NULL;
-static GHashTable *sparql_to_grl_mapping = NULL;
-
-static GVolumeMonitor *volume_monitor = NULL;
-static TrackerSparqlConnection *tracker_connection = NULL;
-static gboolean tracker_per_device_source = FALSE;
-static const GrlPluginInfo *tracker_grl_plugin;
-static guint tracker_dbus_signal_id = 0;
-
-/* =================== Tracker Plugin =============== */
-
-static tracker_evt_update_t *
-tracker_evt_update_new (void)
-{
- tracker_evt_update_t *evt = g_slice_new0 (tracker_evt_update_t);
-
- evt->updated_items = g_hash_table_new (g_direct_hash, g_direct_equal);
-
- return evt;
-}
-
-static void
-tracker_evt_update_free (tracker_evt_update_t *evt)
-{
- if (!evt)
- return;
-
- g_hash_table_destroy (evt->updated_items);
- g_list_free (evt->updated_items_list);
-
- if (evt->cursor != NULL)
- g_object_unref (evt->cursor);
-
- g_slice_free (tracker_evt_update_t, evt);
-}
-
-static void
-grl_tracker_add_source (GrlTrackerSource *source)
-{
- grl_plugin_registry_register_source (grl_plugin_registry_get_default (),
- tracker_grl_plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
-}
-
-static void
-grl_tracker_del_source (GrlTrackerSource *source)
-{
- grl_plugin_registry_unregister_source (grl_plugin_registry_get_default (),
- GRL_MEDIA_PLUGIN (source),
- NULL);
-}
-
-static void
-tracker_evt_update_process_item_cb (GObject *object,
- GAsyncResult *result,
- tracker_evt_update_t *evt)
-{
- const gchar *datasource, *uri;
- gboolean source_mounted;
- gchar *source_name = NULL;
- GrlMediaPlugin *plugin;
- GError *tracker_error = NULL;
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- if (!tracker_sparql_cursor_next_finish (evt->cursor,
- result,
- &tracker_error)) {
- if (tracker_error != NULL) {
- GRL_DEBUG ("\terror in parsing : %s", tracker_error->message);
- g_error_free (tracker_error);
- } else {
- GRL_DEBUG ("\tend of parsing :)");
- }
-
- tracker_evt_update_free (evt);
- return;
- }
-
- datasource = tracker_sparql_cursor_get_string (evt->cursor, 0, NULL);
- uri = tracker_sparql_cursor_get_string (evt->cursor, 1, NULL);
- source_mounted = tracker_sparql_cursor_get_boolean (evt->cursor, 2);
-
- plugin = grl_plugin_registry_lookup_source (grl_plugin_registry_get_default (),
- datasource);
-
- GRL_DEBUG ("\tdatasource=%s uri=%s mounted=%i plugin=%p",
- datasource, uri, source_mounted, plugin);
-
- if (source_mounted) {
- if (plugin == NULL) {
- source_name = get_tracker_source_name (uri, datasource);
-
- plugin = g_object_new (GRL_TRACKER_SOURCE_TYPE,
- "source-id", datasource,
- "source-name", source_name,
- "source-desc", SOURCE_DESC,
- "tracker-connection", tracker_connection,
- NULL);
- grl_tracker_add_source (GRL_TRACKER_SOURCE (plugin));
- g_free (source_name);
- } else {
- GRL_DEBUG ("\tChanges on source %p / %s", plugin, datasource);
- }
- } else if (!source_mounted && plugin != NULL) {
- grl_tracker_del_source (GRL_TRACKER_SOURCE (plugin));
- }
-
- tracker_sparql_cursor_next_async (evt->cursor, NULL,
- (GAsyncReadyCallback) tracker_evt_update_process_item_cb,
- (gpointer) evt);
-}
-
-static void
-tracker_evt_update_process_cb (GObject *object,
- GAsyncResult *result,
- tracker_evt_update_t *evt)
-{
- GError *tracker_error = NULL;
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- evt->cursor = tracker_sparql_connection_query_finish (tracker_connection,
- result, NULL);
-
- if (tracker_error != NULL) {
- GRL_WARNING ("Could not execute sparql query: %s", tracker_error->message);
-
- g_error_free (tracker_error);
- tracker_evt_update_free (evt);
- return;
- }
-
- tracker_sparql_cursor_next_async (evt->cursor, NULL,
- (GAsyncReadyCallback) tracker_evt_update_process_item_cb,
- (gpointer) evt);
-}
-
-static void
-tracker_evt_update_process (tracker_evt_update_t *evt)
-{
- GString *request_str = g_string_new (TRACKER_MOUNTED_DATASOURCES_START);
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- evt->updated_items_iter = evt->updated_items_list;
- g_string_append_printf (request_str, "%i",
- GPOINTER_TO_INT (evt->updated_items_iter->data));
- evt->updated_items_iter = evt->updated_items_iter->next;
-
- while (evt->updated_items_iter != NULL) {
- g_string_append_printf (request_str, ", %i",
- GPOINTER_TO_INT (evt->updated_items_iter->data));
- evt->updated_items_iter = evt->updated_items_iter->next;
- }
-
- g_string_append (request_str, TRACKER_MOUNTED_DATASOURCES_END);
-
- GRL_DEBUG ("\trequest : %s", request_str->str);
-
- tracker_sparql_connection_query_async (tracker_connection,
- request_str->str,
- NULL,
- (GAsyncReadyCallback) tracker_evt_update_process_cb,
- evt);
- g_string_free (request_str, TRUE);
-}
-
-static void
-tracker_dbus_signal_cb (GDBusConnection *connection,
- const gchar *sender_name,
- const gchar *object_path,
- const gchar *interface_name,
- const gchar *signal_name,
- GVariant *parameters,
- gpointer user_data)
-
-{
- gchar *class_name;
- gint graph = 0, subject = 0, predicate = 0, object = 0, subject_state;
- GVariantIter *iter1, *iter2;
- tracker_evt_update_t *evt = tracker_evt_update_new ();
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- g_variant_get (parameters, "(&sa(iiii)a(iiii))", &class_name, &iter1, &iter2);
-
- GRL_DEBUG ("\tTracker update event for class=%s ins=%lu del=%lu",
- class_name,
- (unsigned long) g_variant_iter_n_children (iter1),
- (unsigned long) g_variant_iter_n_children (iter2));
-
- while (g_variant_iter_loop (iter1, "(iiii)", &graph,
- &subject, &predicate, &object)) {
- subject_state = GPOINTER_TO_INT (g_hash_table_lookup (evt->updated_items,
- GSIZE_TO_POINTER (subject)));
-
- if (subject_state == 0) {
- g_hash_table_insert (evt->updated_items,
- GSIZE_TO_POINTER (subject),
- GSIZE_TO_POINTER (1));
- evt->updated_items_list = g_list_append (evt->updated_items_list,
- GSIZE_TO_POINTER (subject));
- } else if (subject_state == 2)
- evt->updated_items_list = g_list_append (evt->updated_items_list,
- GSIZE_TO_POINTER (subject));
- }
- g_variant_iter_free (iter1);
-
-
- while (g_variant_iter_loop (iter2, "(iiii)", &graph,
- &subject, &predicate, &object)) {
- subject_state = GPOINTER_TO_INT (g_hash_table_lookup (evt->updated_items,
- GSIZE_TO_POINTER (subject)));
-
- if (subject_state == 0) {
- g_hash_table_insert (evt->updated_items,
- GSIZE_TO_POINTER (subject),
- GSIZE_TO_POINTER (1));
- evt->updated_items_list = g_list_append (evt->updated_items_list,
- GSIZE_TO_POINTER (subject));
- } else if (subject_state == 2)
- evt->updated_items_list = g_list_append (evt->updated_items_list,
- GSIZE_TO_POINTER (subject));
- }
- g_variant_iter_free (iter2);
-
- evt->updated_items_iter = evt->updated_items_list;
-
- GRL_DEBUG ("\t%u elements updated", g_hash_table_size (evt->updated_items));
- GRL_DEBUG ("\t%u elements updated (list)",
- g_list_length (evt->updated_items_list));
-
- tracker_evt_update_process (evt);
-}
-
-static void
-tracker_dbus_start_watch (void)
-{
- GDBusConnection *connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
-
- tracker_dbus_signal_id = g_dbus_connection_signal_subscribe (connection,
- TRACKER_DBUS_SERVICE,
- TRACKER_DBUS_INTERFACE_RESOURCES,
- "GraphUpdated",
- TRACKER_DBUS_OBJECT_RESOURCES,
- NULL,
- G_DBUS_SIGNAL_FLAGS_NONE,
- tracker_dbus_signal_cb,
- NULL,
- NULL);
-}
-
-static void
-tracker_get_datasource_cb (GObject *object,
- GAsyncResult *result,
- TrackerSparqlCursor *cursor)
-{
- const gchar *datasource, *uri;
- gchar *source_name;
- GError *tracker_error = NULL;
- GrlTrackerSource *source;
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- if (!tracker_sparql_cursor_next_finish (cursor, result, &tracker_error)) {
- if (tracker_error == NULL) {
- GRL_DEBUG ("\tEnd of parsing of devices");
- } else {
- GRL_DEBUG ("\tError while parsing devices: %s", tracker_error->message);
- g_error_free (tracker_error);
- }
- g_object_unref (G_OBJECT (cursor));
- return;
- }
-
- datasource = tracker_sparql_cursor_get_string (cursor, 1, NULL);
- uri = tracker_sparql_cursor_get_string (cursor, 2, NULL);
- source = GRL_TRACKER_SOURCE (grl_plugin_registry_lookup_source (grl_plugin_registry_get_default (),
- datasource));
-
- if (source == NULL) {
- source_name = get_tracker_source_name (uri, datasource); /* TODO: get a better name */
-
- GRL_DEBUG ("\tnew datasource: urn=%s uri=%s\n", datasource, uri);
-
- source = g_object_new (GRL_TRACKER_SOURCE_TYPE,
- "source-id", datasource,
- "source-name", source_name,
- "source-desc", SOURCE_DESC,
- "tracker-connection", tracker_connection,
- NULL);
- grl_plugin_registry_register_source (grl_plugin_registry_get_default (),
- tracker_grl_plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- g_free (source_name);
- }
-
- tracker_sparql_cursor_next_async (cursor, NULL,
- (GAsyncReadyCallback) tracker_get_datasource_cb,
- cursor);
-}
-
-static void
-tracker_get_datasources_cb (GObject *object,
- GAsyncResult *result,
- gpointer data)
-{
- TrackerSparqlCursor *cursor;
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- cursor = tracker_sparql_connection_query_finish (tracker_connection,
- result, NULL);
-
- tracker_sparql_cursor_next_async (cursor, NULL,
- (GAsyncReadyCallback) tracker_get_datasource_cb,
- cursor);
-}
-
-static void
-tracker_get_connection_cb (GObject *object,
- GAsyncResult *res,
- const GrlPluginInfo *plugin)
-{
- /* GrlTrackerSource *source; */
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- tracker_connection = tracker_sparql_connection_get_finish (res, NULL);
-
- if (tracker_connection != NULL) {
- if (tracker_per_device_source == TRUE) {
- /* Let's discover available data sources. */
- GRL_DEBUG ("per device source mode");
-
- tracker_dbus_start_watch ();
-
- volume_monitor = g_volume_monitor_get ();
-
- tracker_sparql_connection_query_async (tracker_connection,
- TRACKER_DATASOURCES_REQUEST,
- NULL,
- (GAsyncReadyCallback) tracker_get_datasources_cb,
- NULL);
- } else {
- /* One source to rule them all. */
- grl_tracker_add_source (grl_tracker_source_new (tracker_connection));
- }
- }
-}
-
-gboolean
-grl_tracker_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GrlConfig *config;
- gint config_count;
-
- GRL_LOG_DOMAIN_INIT (tracker_log_domain, "tracker");
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- tracker_grl_plugin = plugin;
-
- if (!configs) {
- GRL_WARNING ("Configuration not provided! Using default configuration.");
- } else {
- config_count = g_list_length (configs);
- if (config_count > 1) {
- GRL_WARNING ("Provided %i configs, but will only use one", config_count);
- }
-
- config = GRL_CONFIG (configs->data);
-
- tracker_per_device_source = grl_config_get_boolean (config,
- "per-device-source");
- }
-
- tracker_sparql_connection_get_async (NULL,
- (GAsyncReadyCallback) tracker_get_connection_cb,
- (gpointer) plugin);
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_tracker_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Tracker GObject ================ */
-
-static GrlTrackerSource *
-grl_tracker_source_new (TrackerSparqlConnection *connection)
-{
- GRL_DEBUG ("%s", __FUNCTION__);
-
- return g_object_new (GRL_TRACKER_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- "tracker-connection", connection,
- NULL);
-}
-
-G_DEFINE_TYPE (GrlTrackerSource, grl_tracker_source, GRL_TYPE_MEDIA_SOURCE);
-
-static void
-grl_tracker_source_class_init (GrlTrackerSourceClass * klass)
-{
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- GObjectClass *g_class = G_OBJECT_CLASS (klass);
-
- source_class->query = grl_tracker_source_query;
- source_class->metadata = grl_tracker_source_metadata;
- source_class->search = grl_tracker_source_search;
- source_class->browse = grl_tracker_source_browse;
- source_class->cancel = grl_tracker_source_cancel;
-
- metadata_class->supported_keys = grl_tracker_source_supported_keys;
-
- g_class->finalize = grl_tracker_source_finalize;
- g_class->set_property = grl_tracker_source_set_property;
- g_class->constructed = grl_tracker_source_constructed;
-
- g_object_class_install_property (g_class,
- PROP_TRACKER_CONNECTION,
- g_param_spec_object ("tracker-connection",
- "tracker connection",
- "A Tracker connection",
- TRACKER_SPARQL_TYPE_CONNECTION,
- G_PARAM_WRITABLE
- | G_PARAM_CONSTRUCT_ONLY
- | G_PARAM_STATIC_NAME));
-
- g_type_class_add_private (klass, sizeof (GrlTrackerSourcePriv));
-
- setup_key_mappings ();
-}
-
-static void
-grl_tracker_source_init (GrlTrackerSource *source)
-{
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
-
- source->priv = priv;
-
- priv->operations = g_hash_table_new (g_direct_hash, g_direct_equal);
-}
-
-static void
-grl_tracker_source_constructed (GObject *object)
-{
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (object);
-
- if (tracker_per_device_source)
- g_object_get (object, "source-id", &priv->tracker_datasource, NULL);
-}
-
-static void
-grl_tracker_source_finalize (GObject *object)
-{
- GrlTrackerSource *self;
-
- self = GRL_TRACKER_SOURCE (object);
- if (self->priv->tracker_connection)
- g_object_unref (self->priv->tracker_connection);
-
- G_OBJECT_CLASS (grl_tracker_source_parent_class)->finalize (object);
-}
-
-static void
-grl_tracker_source_set_property (GObject *object,
- guint propid,
- const GValue *value,
- GParamSpec *pspec)
-
-{
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (object);
-
- switch (propid) {
- case PROP_TRACKER_CONNECTION:
- if (priv->tracker_connection != NULL)
- g_object_unref (G_OBJECT (priv->tracker_connection));
- priv->tracker_connection = g_object_ref (g_value_get_object (value));
- break;
-
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
- }
-}
-
-/* ======================= Utilities ==================== */
-
-static gchar *
-get_tracker_source_name (const gchar *uri, const gchar *datasource)
-{
- gchar *source_name = NULL;
- GList *mounts, *mount;
- GFile *file;
-
- if (uri != NULL) {
- mounts = g_volume_monitor_get_mounts (volume_monitor);
- file = g_file_new_for_uri (uri);
-
- mount = mounts;
- while (mount != NULL) {
- GFile *m_file = g_mount_get_root (G_MOUNT (mount->data));
-
- if (g_file_equal (m_file, file)) {
- gchar *m_name = g_mount_get_name (G_MOUNT (mount->data));
- g_object_unref (G_OBJECT (m_file));
- source_name = g_strdup_printf ("%s %s", SOURCE_NAME, m_name);
- g_free (m_name);
- break;
- }
- g_object_unref (G_OBJECT (m_file));
-
- mount = mount->next;
- }
- g_list_foreach (mounts, (GFunc) g_object_unref, NULL);
- g_list_free (mounts);
- g_object_unref (G_OBJECT (file));
-
- if (source_name == NULL)
- source_name = g_strdup_printf ("%s %s", SOURCE_NAME, datasource);
- } else {
- source_name = g_strdup (SOURCE_NAME " Local");
- }
-
- return source_name;
-}
-
-static gchar *
-build_flavored_key (gchar *key, const gchar *flavor)
-{
- gint i = 0;
-
- while (key[i] != '\0') {
- if (!g_ascii_isalnum (key[i])) {
- key[i] = '_';
- }
- i++;
- }
-
- return g_strdup_printf ("%s_%s", key, flavor);
-}
-
-static void
-insert_key_mapping (GrlKeyID grl_key,
- const gchar *sparql_key_attr,
- const gchar *sparql_key_flavor)
-{
- tracker_grl_sparql_t *assoc = g_slice_new0 (tracker_grl_sparql_t);
- GList *assoc_list = g_hash_table_lookup (grl_to_sparql_mapping, grl_key);
- gchar *canon_name = g_strdup (g_param_spec_get_name (grl_key));
-
- assoc->grl_key = grl_key;
- assoc->sparql_key_name = build_flavored_key (canon_name, sparql_key_flavor);
- assoc->sparql_key_attr = sparql_key_attr;
- assoc->sparql_key_flavor = sparql_key_flavor;
-
- assoc_list = g_list_append (assoc_list, assoc);
-
- g_hash_table_insert (grl_to_sparql_mapping, grl_key, assoc_list);
- g_hash_table_insert (sparql_to_grl_mapping,
- (gpointer) assoc->sparql_key_name,
- assoc);
- g_hash_table_insert (sparql_to_grl_mapping,
- (gpointer) g_param_spec_get_name (G_PARAM_SPEC (grl_key)),
- assoc);
-
- g_free (canon_name);
-}
-
-static void
-setup_key_mappings (void)
-{
- grl_to_sparql_mapping = g_hash_table_new (g_direct_hash, g_direct_equal);
- sparql_to_grl_mapping = g_hash_table_new (g_str_hash, g_str_equal);
-
- insert_key_mapping (GRL_METADATA_KEY_ALBUM,
- "nmm:albumTitle(nmm:musicAlbum(?urn))",
- "audio");
-
- insert_key_mapping (GRL_METADATA_KEY_ARTIST,
- "nmm:artistName(nmm:performer(?urn))",
- "audio");
-
- insert_key_mapping (GRL_METADATA_KEY_AUTHOR,
- "nmm:artistName(nmm:performer(?urn))",
- "audio");
-
- insert_key_mapping (GRL_METADATA_KEY_BITRATE,
- "nfo:averageBitrate(?urn)",
- "audio");
-
- insert_key_mapping (GRL_METADATA_KEY_CHILDCOUNT,
- "nfo:entryCounter(?urn)",
- "directory");
-
- insert_key_mapping (GRL_METADATA_KEY_DATE,
- "nfo:fileLastModified(?urn)",
- "file");
-
- insert_key_mapping (GRL_METADATA_KEY_DURATION,
- "nfo:duration(?urn)",
- "audio");
-
- insert_key_mapping (GRL_METADATA_KEY_FRAMERATE,
- "nfo:frameRate(?urn)",
- "video");
-
- insert_key_mapping (GRL_METADATA_KEY_HEIGHT,
- "nfo:height(?urn)",
- "video");
-
- insert_key_mapping (GRL_METADATA_KEY_ID,
- "?urn",
- "file");
-
- insert_key_mapping (GRL_METADATA_KEY_LAST_PLAYED,
- "nfo:fileLastAccessed(?urn)",
- "file");
-
- insert_key_mapping (GRL_METADATA_KEY_MIME,
- "nie:mimeType(?urn)",
- "file");
-
- insert_key_mapping (GRL_METADATA_KEY_SITE,
- "nie:url(?urn)",
- "file");
-
- insert_key_mapping (GRL_METADATA_KEY_TITLE,
- "nie:title(?urn)",
- "audio");
-
- insert_key_mapping (GRL_METADATA_KEY_TITLE,
- "nfo:fileName(?urn)",
- "file");
-
- insert_key_mapping (GRL_METADATA_KEY_URL,
- "nie:url(?urn)",
- "file");
-
- insert_key_mapping (GRL_METADATA_KEY_WIDTH,
- "nfo:width(?urn)",
- "video");
-}
-
-static tracker_grl_sparql_t *
-get_mapping_from_sparql (const gchar *key)
-{
- return (tracker_grl_sparql_t *) g_hash_table_lookup (sparql_to_grl_mapping,
- key);
-}
-
-static GList *
-get_mapping_from_grl (const GrlKeyID key)
-{
- return (GList *) g_hash_table_lookup (grl_to_sparql_mapping, key);
-}
-
-static struct OperationSpec *
-tracker_operation_initiate (GrlMediaSource *source,
- GrlTrackerSourcePriv *priv,
- guint operation_id)
-{
- struct OperationSpec *os = g_slice_new0 (struct OperationSpec);
-
- os->source = source;
- os->priv = priv;
- os->operation_id = operation_id;
- os->cancel_op = g_cancellable_new ();
-
- g_hash_table_insert (priv->operations, GSIZE_TO_POINTER (operation_id), os);
-
- return os;
-}
-
-static void
-tracker_operation_terminate (struct OperationSpec *os)
-{
- if (os == NULL)
- return;
-
- g_hash_table_remove (os->priv->operations,
- GSIZE_TO_POINTER (os->operation_id));
-
- g_object_unref (G_OBJECT (os->cursor));
- g_object_unref (G_OBJECT (os->cancel_op));
- g_slice_free (struct OperationSpec, os);
-}
-
-static gchar *
-get_select_string (GrlMediaSource *source, const GList *keys)
-{
- const GList *key = keys;
- GString *gstr = g_string_new ("");
- GList *assoc_list;
- tracker_grl_sparql_t *assoc;
-
- while (key != NULL) {
- assoc_list = get_mapping_from_grl ((GrlKeyID) key->data);
- while (assoc_list != NULL) {
- assoc = (tracker_grl_sparql_t *) assoc_list->data;
- if (assoc != NULL) {
- g_string_append_printf (gstr, "%s AS %s",
- assoc->sparql_key_attr,
- assoc->sparql_key_name);
- g_string_append (gstr, " ");
- }
- assoc_list = assoc_list->next;
- }
- key = key->next;
- }
-
- return g_string_free (gstr, FALSE);
-}
-
-/* Builds an appropriate GrlMedia based on ontology type returned by tracker, or
- NULL if unknown */
-static GrlMedia *
-build_grilo_media (const gchar *rdf_type)
-{
- GrlMedia *media = NULL;
- gchar **rdf_single_type;
- int i;
-
- if (!rdf_type) {
- return NULL;
- }
-
- /* As rdf_type can be formed by several types, split them */
- rdf_single_type = g_strsplit (rdf_type, ",", -1);
- i = g_strv_length (rdf_single_type) - 1;
-
- while (!media && i >= 0) {
- if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_MUSIC)) {
- media = grl_media_audio_new ();
- } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_VIDEO)) {
- media = grl_media_video_new ();
- } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_IMAGE)) {
- media = grl_media_image_new ();
- } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_ARTIST)) {
- media = grl_media_box_new ();
- } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_ALBUM)) {
- media = grl_media_box_new ();
- } else if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_BOX)) {
- media = grl_media_box_new ();
- }
- i--;
- }
-
- g_strfreev (rdf_single_type);
-
- return media;
-}
-
-static void
-fill_grilo_media_from_sparql (GrlMedia *media,
- TrackerSparqlCursor *cursor,
- gint column)
-{
- const gchar *sparql_key = tracker_sparql_cursor_get_variable_name (cursor, column);
- tracker_grl_sparql_t *assoc = get_mapping_from_sparql (sparql_key);;
- union {
- gint int_val;
- gdouble double_val;
- const gchar *str_val;
- } val;
-
- if (assoc == NULL)
- return;
-
- GRL_DEBUG ("\tSetting media prop (col=%i/var=%s/prop=%s) %s",
- column,
- sparql_key,
- g_param_spec_get_name (G_PARAM_SPEC (assoc->grl_key)),
- tracker_sparql_cursor_get_string (cursor, column, NULL));
-
- if (tracker_sparql_cursor_is_bound (cursor, column) == FALSE) {
- GRL_DEBUG ("\t\tDropping, no data");
- return;
- }
-
- if (grl_data_has_key (GRL_DATA (media), assoc->grl_key)) {
- GRL_DEBUG ("\t\tDropping, already here");
- return;
- }
-
- switch (G_PARAM_SPEC (assoc->grl_key)->value_type) {
- case G_TYPE_STRING:
- val.str_val = tracker_sparql_cursor_get_string (cursor, column, NULL);
- if (val.str_val != NULL)
- grl_data_set_string (GRL_DATA (media), assoc->grl_key, val.str_val);
- break;
-
- case G_TYPE_INT:
- val.int_val = tracker_sparql_cursor_get_integer (cursor, column);
- grl_data_set_int (GRL_DATA (media), assoc->grl_key, val.int_val);
- break;
-
- case G_TYPE_FLOAT:
- val.double_val = tracker_sparql_cursor_get_double (cursor, column);
- grl_data_set_float (GRL_DATA (media), assoc->grl_key, (gfloat) val.double_val);
- break;
-
- default:
- GRL_DEBUG ("\t\tUnexpected data type");
- break;
- }
-}
-
-static void
-tracker_query_result_cb (GObject *source_object,
- GAsyncResult *result,
- struct OperationSpec *operation)
-{
- gint col;
- const gchar *sparql_type;
- GError *tracker_error = NULL, *error = NULL;
- GrlMedia *media;
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- if (g_cancellable_is_cancelled (operation->cancel_op)) {
- GRL_DEBUG ("\tOperation %u cancelled", operation->operation_id);
- operation->callback (operation->source,
- operation->operation_id,
- NULL, 0,
- operation->user_data, NULL);
- tracker_operation_terminate (operation);
-
- return;
- }
-
- if (!tracker_sparql_cursor_next_finish (operation->cursor,
- result,
- &tracker_error)) {
- if (tracker_error != NULL) {
- GRL_DEBUG ("\terror in parsing : %s", tracker_error->message);
-
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Failed to start browse action : %s",
- tracker_error->message);
-
- operation->callback (operation->source,
- operation->operation_id,
- NULL, 0,
- operation->user_data, error);
-
- g_error_free (error);
- g_error_free (tracker_error);
- } else {
- GRL_DEBUG ("\tend of parsing :)");
-
- /* Only emit this last one if more result than expected */
- if (operation->count > 1)
- operation->callback (operation->source,
- operation->operation_id,
- NULL, 0,
- operation->user_data, NULL);
- }
-
- tracker_operation_terminate (operation);
- return;
- }
-
- sparql_type = tracker_sparql_cursor_get_string (operation->cursor, 0, NULL);
-
- GRL_DEBUG ("Parsing line %i of type %s", operation->current, sparql_type);
-
- media = build_grilo_media (sparql_type);
-
- if (media != NULL) {
- for (col = 1 ;
- col < tracker_sparql_cursor_get_n_columns (operation->cursor) ;
- col++) {
- fill_grilo_media_from_sparql (media, operation->cursor, col);
- }
-
- operation->callback (operation->source,
- operation->operation_id,
- media,
- --operation->count,
- operation->user_data,
- NULL);
- }
-
- /* Schedule the next line to parse */
- operation->current++;
- if (operation->count < 1)
- tracker_operation_terminate (operation);
- else
- tracker_sparql_cursor_next_async (operation->cursor, operation->cancel_op,
- (GAsyncReadyCallback) tracker_query_result_cb,
- (gpointer) operation);
-}
-
-static void
-tracker_query_cb (GObject *source_object,
- GAsyncResult *result,
- struct OperationSpec *operation)
-{
- GError *tracker_error = NULL, *error = NULL;
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- operation->cursor =
- tracker_sparql_connection_query_finish (operation->priv->tracker_connection,
- result, &tracker_error);
-
- if (tracker_error) {
- GRL_WARNING ("Could not execute sparql query: %s", tracker_error->message);
-
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Failed to start browse action : %s",
- tracker_error->message);
-
- operation->callback (operation->source, operation->operation_id, NULL, 0,
- operation->user_data, error);
-
- g_error_free (tracker_error);
- g_error_free (error);
- g_slice_free (struct OperationSpec, operation);
-
- return;
- }
-
- /* Start parsing results */
- operation->current = 0;
- tracker_sparql_cursor_next_async (operation->cursor, NULL,
- (GAsyncReadyCallback) tracker_query_result_cb,
- (gpointer) operation);
-}
-
-static void
-tracker_metadata_cb (GObject *source_object,
- GAsyncResult *result,
- GrlMediaSourceMetadataSpec *ms)
-{
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (ms->source);
- gint col;
- GError *tracker_error = NULL, *error = NULL;
- TrackerSparqlCursor *cursor;
-
- GRL_DEBUG ("%s", __FUNCTION__);
-
- cursor = tracker_sparql_connection_query_finish (priv->tracker_connection,
- result, &tracker_error);
-
- if (tracker_error) {
- GRL_WARNING ("Could not execute sparql query: %s", tracker_error->message);
-
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Failed to start browse action : %s",
- tracker_error->message);
-
- ms->callback (ms->source, NULL, ms->user_data, error);
-
- g_error_free (tracker_error);
- g_error_free (error);
-
- goto end_operation;
- }
-
-
- tracker_sparql_cursor_next (cursor, NULL, NULL);
-
- /* Translate Sparql result into Grilo result */
- for (col = 0 ; col < tracker_sparql_cursor_get_n_columns (cursor) ; col++) {
- fill_grilo_media_from_sparql (ms->media, cursor, col);
- }
-
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
-
- end_operation:
- if (cursor)
- g_object_unref (G_OBJECT (cursor));
-}
-
-static gchar *
-tracker_source_get_device_constraint (GrlTrackerSourcePriv *priv)
-{
- if (priv->tracker_datasource == NULL)
- return g_strdup ("");
-
- return g_strdup_printf ("?urn nie:dataSource <%s> .",
- priv->tracker_datasource);
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_tracker_source_supported_keys (GrlMetadataSource *source)
-{
- return grl_plugin_registry_get_metadata_keys (grl_plugin_registry_get_default ());
-}
-
-/**
- * Query is a SPARQL query.
- *
- * Columns must be named with the Grilo key name that the column
- * represent. Unnamed or unknown columns will be ignored.
- *
- * First column must be the media type, and it does not need to be named. It
- * must match with any value supported in rdf:type() property, or
- * grilo#Box. Types understood are:
- *
- * <itemizedlist>
- * <listitem>
- * <para>
- * <literal>nmm#MusicPiece</literal>
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * <literal>nmm#Video</literal>
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * <literal>nmm#Photo</literal>
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * <literal>nmm#Artist</literal>
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * <literal>nmm#MusicAlbum</literal>
- * </para>
- * </listitem>
- * <listitem>
- * <para>
- * <literal>grilo#Box</literal>
- * </para>
- * </listitem>
- * </itemizedlist>
- *
- * An example for searching all songs:
- *
- * <informalexample>
- * <programlisting>
- * SELECT rdf:type(?song)
- * ?song AS id
- * nie:title(?song) AS title
- * nie:url(?song) AS url
- * WHERE { ?song a nmm:MusicPiece }
- * </programlisting>
- * </informalexample>
- *
- * Alternatively, we can use a partial SPARQL query: just specify the sentence
- * in the WHERE part. In this case, "?urn" is the ontology concept to be used in
- * the clause.
- *
- * An example of such partial query:
- *
- * <informalexample>
- * <programlisting>
- * ?urn a nfo:Media
- * </programlisting>
- * </informalexample>
- *
- * In this case, all data required to build a full SPARQL query will be get from
- * the query spec.
- */
-static void
-grl_tracker_source_query (GrlMediaSource *source,
- GrlMediaSourceQuerySpec *qs)
-{
- GError *error = NULL;
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
- gchar *constraint;
- gchar *sparql_final;
- gchar *sparql_select;
- struct OperationSpec *os;
-
- GRL_DEBUG ("%s: id=%u", __FUNCTION__, qs->query_id);
-
- if (!qs->query || qs->query[0] == '\0') {
- error = g_error_new_literal (GRL_CORE_ERROR,
- GRL_CORE_ERROR_QUERY_FAILED,
- "Empty query");
- goto send_error;
- }
-
- /* Check if it is a full sparql query */
- if (g_ascii_strncasecmp (qs->query, "select ", 7) != 0) {
- constraint = tracker_source_get_device_constraint (priv);
- sparql_select = get_select_string (source, qs->keys);
- sparql_final = g_strdup_printf (TRACKER_QUERY_REQUEST,
- sparql_select,
- qs->query,
- constraint,
- qs->skip,
- qs->count);
- g_free (constraint);
- g_free (qs->query);
- g_free (sparql_select);
- qs->query = sparql_final;
- grl_tracker_source_query (source, qs);
- return;
- }
-
- GRL_DEBUG ("select : %s", qs->query);
-
- os = tracker_operation_initiate (source, priv, qs->query_id);
- os->keys = qs->keys;
- os->skip = qs->skip;
- os->count = qs->count;
- os->callback = qs->callback;
- os->user_data = qs->user_data;
-
- tracker_sparql_connection_query_async (priv->tracker_connection,
- qs->query,
- os->cancel_op,
- (GAsyncReadyCallback) tracker_query_cb,
- os);
-
- return;
-
- send_error:
- qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
- g_error_free (error);
-}
-
-static void
-grl_tracker_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
- gchar *sparql_select, *sparql_final;
-
- GRL_DEBUG ("%s: id=%i", __FUNCTION__, ms->metadata_id);
-
- sparql_select = get_select_string (source, ms->keys);
- sparql_final = g_strdup_printf (TRACKER_METADATA_REQUEST, sparql_select,
- grl_media_get_id (ms->media));
-
- GRL_DEBUG ("select: '%s'", sparql_final);
-
- tracker_sparql_connection_query_async (priv->tracker_connection,
- sparql_final,
- NULL,
- (GAsyncReadyCallback) tracker_metadata_cb,
- ms);
-
- if (sparql_select != NULL)
- g_free (sparql_select);
- if (sparql_final != NULL)
- g_free (sparql_final);
-}
-
-static void
-grl_tracker_source_search (GrlMediaSource *source, GrlMediaSourceSearchSpec *ss)
-{
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
- gchar *constraint;
- gchar *sparql_select;
- gchar *sparql_final;
- struct OperationSpec *os;
-
- GRL_DEBUG ("%s: id=%u", __FUNCTION__, ss->search_id);
-
- constraint = tracker_source_get_device_constraint (priv);
- sparql_select = get_select_string (source, ss->keys);
- if (!ss->text || ss->text[0] == '\0') {
- /* Search all */
- sparql_final = g_strdup_printf (TRACKER_SEARCH_ALL_REQUEST, sparql_select,
- constraint, ss->skip, ss->count);
- } else {
- sparql_final = g_strdup_printf (TRACKER_SEARCH_REQUEST, sparql_select,
- ss->text, constraint, ss->skip, ss->count);
- }
-
- GRL_DEBUG ("select: '%s'", sparql_final);
-
- os = tracker_operation_initiate (source, priv, ss->search_id);
- os->keys = ss->keys;
- os->skip = ss->skip;
- os->count = ss->count;
- os->callback = ss->callback;
- os->user_data = ss->user_data;
-
- tracker_sparql_connection_query_async (priv->tracker_connection,
- sparql_final,
- os->cancel_op,
- (GAsyncReadyCallback) tracker_query_cb,
- os);
-
- g_free (constraint);
- g_free (sparql_select);
- g_free (sparql_final);
-}
-
-static void
-grl_tracker_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
- gchar *constraint;
- gchar *sparql_select;
- gchar *sparql_final;
- struct OperationSpec *os;
- GrlMedia *media;
-
- GRL_DEBUG ("%s: id=%u", __FUNCTION__, bs->browse_id);
-
- if ((bs->container == NULL || grl_media_get_id (bs->container) == NULL)) {
- /* Hardcoded categories */
- media = grl_media_box_new ();
- grl_media_set_title (media, "Music");
- grl_media_set_id (media, "nmm:MusicPiece");
- bs->callback (bs->source, bs->browse_id, media, 2, bs->user_data, NULL);
-
- media = grl_media_box_new ();
- grl_media_set_title (media, "Photo");
- grl_media_set_id (media, "nmm:Photo");
- bs->callback (bs->source, bs->browse_id, media, 1, bs->user_data, NULL);
-
- media = grl_media_box_new ();
- grl_media_set_title (media, "Video");
- grl_media_set_id (media, "nmm:Video");
- bs->callback (bs->source, bs->browse_id, media, 0, bs->user_data, NULL);
- return;
- }
-
- constraint = tracker_source_get_device_constraint (priv);
- sparql_select = get_select_string (bs->source, bs->keys);
- sparql_final = g_strdup_printf (TRACKER_BROWSE_CATEGORY_REQUEST,
- sparql_select,
- grl_media_get_id (bs->container),
- constraint,
- bs->skip, bs->count);
-
- GRL_DEBUG ("select: '%s'", sparql_final);
-
- os = tracker_operation_initiate (source, priv, bs->browse_id);
- os->keys = bs->keys;
- os->skip = bs->skip;
- os->count = bs->count;
- os->callback = bs->callback;
- os->user_data = bs->user_data;
-
- tracker_sparql_connection_query_async (priv->tracker_connection,
- sparql_final,
- os->cancel_op,
- (GAsyncReadyCallback) tracker_query_cb,
- os);
-
- g_free (constraint);
- g_free (sparql_select);
- g_free (sparql_final);
-}
-
-static void
-grl_tracker_source_cancel (GrlMediaSource *source, guint operation_id)
-{
- GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source);
- struct OperationSpec *os;
-
- GRL_DEBUG ("%s: id=%u", __FUNCTION__, operation_id);
-
- os = g_hash_table_lookup (priv->operations, GSIZE_TO_POINTER (operation_id));
-
- if (os != NULL)
- g_cancellable_cancel (os->cancel_op);
-}
diff --git a/src/tracker/grl-tracker.h b/src/tracker/grl-tracker.h
deleted file mode 100644
index ddf46ce..0000000
--- a/src/tracker/grl-tracker.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2011 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Juan A. Suarez Romero <jasuarez igalia 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_TRACKER_H_
-#define _GRL_TRACKER_H_
-
-#include <grilo.h>
-
-#define GRL_TRACKER_SOURCE_TYPE \
- (grl_tracker_source_get_type ())
-
-#define GRL_TRACKER_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_TRACKER_SOURCE_TYPE, \
- GrlTrackerSource))
-
-#define GRL_IS_TRACKER_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_TRACKER_SOURCE_TYPE))
-
-#define GRL_TRACKER_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_TRACKER_SOURCE_TYPE, \
- GrlTrackerSourceClass))
-
-#define GRL_IS_TRACKER_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_TRACKER_SOURCE_TYPE))
-
-#define GRL_TRACKER_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_TRACKER_SOURCE_TYPE, \
- GrlTrackerSourceClass))
-
-typedef struct _GrlTrackerSource GrlTrackerSource;
-typedef struct _GrlTrackerSourcePriv GrlTrackerSourcePriv;
-
-struct _GrlTrackerSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlTrackerSourcePriv *priv;
-
-};
-
-typedef struct _GrlTrackerSourceClass GrlTrackerSourceClass;
-
-struct _GrlTrackerSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_tracker_source_get_type (void);
-
-#endif /* _GRL_TRACKER_H_ */
diff --git a/src/tracker/grl-tracker.xml b/src/tracker/grl-tracker.xml
deleted file mode 100644
index 20f7bb6..0000000
--- a/src/tracker/grl-tracker.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Tracker</name>
- <description>A plugin for searching multimedia content using Tracker</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/upnp/Makefile.am b/src/upnp/Makefile.am
deleted file mode 100644
index b6b09bd..0000000
--- a/src/upnp/Makefile.am
+++ /dev/null
@@ -1,46 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Iago Toral Quiroga <itoral igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlupnp.la
-
-libgrlupnp_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GUPNP_CFLAGS) \
- $(GUPNPAV_CFLAGS) \
- $(GTHREAD_CFLAGS) \
- $(XML_CFLAGS)
-
-libgrlupnp_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GUPNP_LIBS) \
- $(GUPNPAV_LIBS) \
- $(GTHREAD_LIBS) \
- $(XML_LIBS)
-
-libgrlupnp_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlupnp_la_CFLAGS += \
- $(XML_CFLAGS)
-
-libgrlupnp_la_LIBADD += \
- $(XML_LIBS)
-
-libgrlupnp_la_SOURCES = grl-upnp.c grl-upnp.h
-
-libdir = $(GRL_PLUGINS_DIR)
-upnpxmldir = $(GRL_PLUGINS_CONF_DIR)
-upnpxml_DATA = $(UPNP_PLUGIN_ID).xml
-
-EXTRA_DIST = $(upnpxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/upnp/grl-upnp.c b/src/upnp/grl-upnp.c
deleted file mode 100644
index f024b53..0000000
--- a/src/upnp/grl-upnp.c
+++ /dev/null
@@ -1,1356 +0,0 @@
-/*
- * Copyright (C) 2010, 2011 Igalia S.L.
- * Copyright (C) 2011 Intel Corporation.
- *
- * This component is based on Maemo's mafw-upnp-source source code.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
-#include <libgupnp/gupnp.h>
-#include <libgupnp-av/gupnp-av.h>
-#include <string.h>
-#include <stdlib.h>
-#include <libxml/tree.h>
-
-#include "grl-upnp.h"
-
-#define GRL_UPNP_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), GRL_UPNP_SOURCE_TYPE, GrlUpnpPrivate))
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT upnp_log_domain
-GRL_LOG_DOMAIN_STATIC(upnp_log_domain);
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID UPNP_PLUGIN_ID
-
-#define SOURCE_ID_TEMPLATE "grl-upnp-%s"
-#define SOURCE_NAME_TEMPLATE "UPnP - %s"
-#define SOURCE_DESC_TEMPLATE "A source for browsing the UPnP server '%s'"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-/* --- Other --- */
-
-#ifndef CONTENT_DIR_SERVICE
-#define CONTENT_DIR_SERVICE "urn:schemas-upnp-org:service:ContentDirectory"
-#endif
-
-#define UPNP_SEARCH_SPEC \
- "dc:title contains \"%s\" or " \
- "upnp:album contains \"%s\" or " \
- "upnp:artist contains \"%s\""
-
-#define UPNP_SEARCH_ALL \
- "upnp:class derivedfrom \"object.item\""
-
-struct _GrlUpnpPrivate {
- GUPnPDeviceProxy* device;
- GUPnPServiceProxy* service;
- GUPnPControlPoint *cp;
- gboolean search_enabled;
- gchar *upnp_name;
-};
-
-struct OperationSpec {
- GrlMediaSource *source;
- guint operation_id;
- GList *keys;
- guint skip;
- guint count;
- GrlMediaSourceResultCb callback;
- gpointer user_data;
-};
-
-struct SourceInfo {
- gchar *source_id;
- gchar *source_name;
- GUPnPDeviceProxy* device;
- GUPnPServiceProxy* service;
- GrlPluginInfo *plugin;
-};
-
-static void setup_key_mappings (void);
-
-static gchar *build_source_id (const gchar *udn);
-
-static GrlUpnpSource *grl_upnp_source_new (const gchar *id, const gchar *name);
-
-gboolean grl_upnp_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-static void grl_upnp_source_finalize (GObject *plugin);
-
-static const GList *grl_upnp_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_upnp_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-
-static void grl_upnp_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-
-static void grl_upnp_source_query (GrlMediaSource *source,
- GrlMediaSourceQuerySpec *qs);
-
-static void grl_upnp_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms);
-
-static GrlSupportedOps grl_upnp_source_supported_operations (GrlMetadataSource *source);
-
-static gboolean grl_upnp_source_notify_change_start (GrlMediaSource *source,
- GError **error);
-
-static gboolean grl_upnp_source_notify_change_stop (GrlMediaSource *source,
- GError **error);
-
-static void context_available_cb (GUPnPContextManager *context_manager,
- GUPnPContext *context,
- gpointer user_data);
-static void device_available_cb (GUPnPControlPoint *cp,
- GUPnPDeviceProxy *device,
- gpointer user_data);
-static void device_unavailable_cb (GUPnPControlPoint *cp,
- GUPnPDeviceProxy *device,
- gpointer user_data);
-
-/* ===================== Globals ================= */
-
-static GHashTable *key_mapping = NULL;
-static GHashTable *filter_key_mapping = NULL;
-static GUPnPContextManager *context_manager = NULL;
-
-/* =================== UPnP Plugin =============== */
-
-gboolean
-grl_upnp_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- GRL_LOG_DOMAIN_INIT (upnp_log_domain, "upnp");
-
- GRL_DEBUG ("grl_upnp_plugin_init");
-
- /* libsoup needs this */
- if (!g_thread_supported()) {
- g_thread_init (NULL);
- }
-
- context_manager = gupnp_context_manager_new (NULL, 0);
- g_signal_connect (context_manager,
- "context-available",
- G_CALLBACK (context_available_cb),
- (gpointer)plugin);
-
- return TRUE;
-}
-
-static void
-grl_upnp_plugin_deinit (void)
-{
- GRL_DEBUG ("grl_upnp_plugin_deinit");
-
- if (context_manager != NULL) {
- g_object_unref (context_manager);
- context_manager = NULL;
- }
-}
-
-GRL_PLUGIN_REGISTER (grl_upnp_plugin_init,
- grl_upnp_plugin_deinit,
- PLUGIN_ID);
-
-/* ================== UPnP GObject ================ */
-
-G_DEFINE_TYPE (GrlUpnpSource, grl_upnp_source, GRL_TYPE_MEDIA_SOURCE);
-
-static GrlUpnpSource *
-grl_upnp_source_new (const gchar *source_id, const gchar *name)
-{
- gchar *source_name, *source_desc;
- GrlUpnpSource *source;
-
- GRL_DEBUG ("grl_upnp_source_new");
- source_name = g_strdup_printf (SOURCE_NAME_TEMPLATE, name);
- source_desc = g_strdup_printf (SOURCE_DESC_TEMPLATE, name);
-
- source = g_object_new (GRL_UPNP_SOURCE_TYPE,
- "source-id", source_id,
- "source-name", source_name,
- "source-desc", source_desc,
- NULL);
-
- source->priv->upnp_name = g_strdup (name);
-
- g_free (source_name);
- g_free (source_desc);
-
- return source;
-}
-
-static void
-grl_upnp_source_class_init (GrlUpnpSourceClass * klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
-
- gobject_class->finalize = grl_upnp_source_finalize;
-
- metadata_class->supported_keys = grl_upnp_source_supported_keys;
- metadata_class->supported_operations = grl_upnp_source_supported_operations;
-
- source_class->browse = grl_upnp_source_browse;
- source_class->search = grl_upnp_source_search;
- source_class->query = grl_upnp_source_query;
- source_class->metadata = grl_upnp_source_metadata;
- source_class->notify_change_start = grl_upnp_source_notify_change_start;
- source_class->notify_change_stop = grl_upnp_source_notify_change_stop;
-
- g_type_class_add_private (klass, sizeof (GrlUpnpPrivate));
-
- setup_key_mappings ();
-}
-
-static void
-grl_upnp_source_init (GrlUpnpSource *source)
-{
- source->priv = GRL_UPNP_GET_PRIVATE (source);
-}
-
-static void
-grl_upnp_source_finalize (GObject *object)
-{
- GrlUpnpSource *source;
-
- GRL_DEBUG ("grl_upnp_source_finalize");
-
- source = GRL_UPNP_SOURCE (object);
-
- g_object_unref (source->priv->device);
- g_object_unref (source->priv->service);
- g_free (source->priv->upnp_name);
-
- G_OBJECT_CLASS (grl_upnp_source_parent_class)->finalize (object);
-}
-
-/* ======================= Utilities ==================== */
-
-static gchar *
-build_source_id (const gchar *udn)
-{
- return g_strdup_printf (SOURCE_ID_TEMPLATE, udn);
-}
-
-static void
-free_source_info (struct SourceInfo *info)
-{
- g_free (info->source_id);
- g_free (info->source_name);
- g_object_unref (info->device);
- g_object_unref (info->service);
- g_slice_free (struct SourceInfo, info);
-}
-
-static void
-container_changed_cb (GUPnPServiceProxy *proxy,
- const char *variable,
- GValue *value,
- gpointer user_data)
-{
- GrlMedia *container;
- GrlMediaSource *source = GRL_MEDIA_SOURCE (user_data);
- gchar **tokens;
- gint i = 0;
-
- GRL_DEBUG (__func__);
-
- /* Value is a list of pairs (id, number), where "id" is the container id */
- tokens = g_strsplit (g_value_get_string (value), ",", -1);
- while (tokens[i]) {
- container = grl_media_box_new ();
- grl_media_set_id (container, tokens[i]);
- grl_media_source_notify_change (source,
- container,
- GRL_CONTENT_CHANGED,
- FALSE);
- g_object_unref (container);
- i += 2;
- }
- g_strfreev (tokens);
-}
-
-static void
-gupnp_search_caps_cb (GUPnPServiceProxy *service,
- GUPnPServiceProxyAction *action,
- gpointer user_data)
-{
- GError *error = NULL;
- gchar *caps = NULL;
- gchar *name;
- GrlUpnpSource *source;
- gchar *source_id;
- GrlPluginRegistry *registry;
- struct SourceInfo *source_info;
- gboolean result;
-
- result =
- gupnp_service_proxy_end_action (service, action, &error,
- "SearchCaps", G_TYPE_STRING, &caps,
- NULL);
- if (!result) {
- GRL_WARNING ("Failed to execute GetSearchCaps operation");
- if (error) {
- GRL_WARNING ("Reason: %s", error->message);
- g_error_free (error);
- }
- }
-
- source_info = (struct SourceInfo *) user_data;
- name = source_info->source_name;
- source_id = source_info->source_id;
-
- registry = grl_plugin_registry_get_default ();
- if (grl_plugin_registry_lookup_source (registry, source_id)) {
- GRL_DEBUG ("A source with id '%s' is already registered. Skipping...",
- source_id);
- goto free_resources;
- }
-
- source = grl_upnp_source_new (source_id, name);
- source->priv->device = g_object_ref (source_info->device);
- source->priv->service = g_object_ref (source_info->service);
-
- GRL_DEBUG ("Search caps for source '%s': '%s'", name, caps);
-
- if (caps && caps[0] != '\0') {
- GRL_DEBUG ("Setting search enabled for source '%s'", name );
- source->priv->search_enabled = TRUE;
- } else {
- GRL_DEBUG ("Setting search disabled for source '%s'", name );
- }
-
- grl_plugin_registry_register_source (registry,
- source_info->plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
-
- free_resources:
- free_source_info (source_info);
-}
-
-static void
-context_available_cb (GUPnPContextManager *context_manager,
- GUPnPContext *context,
- gpointer user_data)
-{
- GUPnPControlPoint *cp;
-
- GRL_DEBUG ("%s", __func__);
-
- cp = gupnp_control_point_new (context, "urn:schemas-upnp-org:device:MediaServer:1");
- g_signal_connect (cp,
- "device-proxy-available",
- G_CALLBACK (device_available_cb),
- user_data);
- g_signal_connect (cp,
- "device-proxy-unavailable",
- G_CALLBACK (device_unavailable_cb),
- NULL);
-
- gssdp_resource_browser_set_active (GSSDP_RESOURCE_BROWSER (cp), TRUE);
-
- /* Let context manager take care of the control point life cycle */
- gupnp_context_manager_manage_control_point (context_manager, cp);
- g_object_unref (cp);
-}
-
-static void
-device_available_cb (GUPnPControlPoint *cp,
- GUPnPDeviceProxy *device,
- gpointer user_data)
-{
- gchar* name;
- const gchar* udn;
- const char *type;
- GUPnPServiceInfo *service;
- GrlPluginRegistry *registry;
- gchar *source_id;
-
- GRL_DEBUG ("device_available_cb");
-
- type = gupnp_device_info_get_device_type (GUPNP_DEVICE_INFO (device));
- GRL_DEBUG (" type: %s", type);
-
- service = gupnp_device_info_get_service (GUPNP_DEVICE_INFO (device),
- CONTENT_DIR_SERVICE);
- if (!service) {
- GRL_DEBUG ("Device does not provide required service, ignoring...");
- return;
- }
-
- udn = gupnp_device_info_get_udn (GUPNP_DEVICE_INFO (device));
- GRL_DEBUG (" udn: %s ", udn);
-
- name = gupnp_device_info_get_friendly_name (GUPNP_DEVICE_INFO (device));
- GRL_DEBUG (" name: %s", name);
-
- registry = grl_plugin_registry_get_default ();
- source_id = build_source_id (udn);
- if (grl_plugin_registry_lookup_source (registry, source_id)) {
- GRL_DEBUG ("A source with id '%s' is already registered. Skipping...",
- source_id);
- goto free_resources;
- }
-
- /* We got a valid UPnP source */
- /* Now let's check if it supports search operations before registering */
- struct SourceInfo *source_info = g_slice_new0 (struct SourceInfo);
- source_info->source_id = g_strdup (source_id);
- source_info->source_name = g_strdup (name);
- source_info->device = g_object_ref (device);
- source_info->service = g_object_ref (service);
- source_info->plugin = (GrlPluginInfo *) user_data;
-
- if (!gupnp_service_proxy_begin_action (GUPNP_SERVICE_PROXY (service),
- "GetSearchCapabilities",
- gupnp_search_caps_cb,
- source_info,
- NULL)) {
- GrlUpnpSource *source = grl_upnp_source_new (source_id, name);
- GRL_WARNING ("Failed to start GetCapabilitiesSearch action");
- GRL_DEBUG ("Setting search disabled for source '%s'", name );
- registry = grl_plugin_registry_get_default ();
- grl_plugin_registry_register_source (registry,
- source_info->plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- free_source_info (source_info);
- }
-
- free_resources:
- g_object_unref (service);
- g_free (source_id);
-}
-
-static void
-device_unavailable_cb (GUPnPControlPoint *cp,
- GUPnPDeviceProxy *device,
- gpointer user_data)
-{
- const gchar* udn;
- GrlMediaPlugin *source;
- GrlPluginRegistry *registry;
- gchar *source_id;
-
- GRL_DEBUG ("device_unavailable_cb");
-
- udn = gupnp_device_info_get_udn (GUPNP_DEVICE_INFO (device));
- GRL_DEBUG (" udn: %s ", udn);
-
- registry = grl_plugin_registry_get_default ();
- source_id = build_source_id (udn);
- source = grl_plugin_registry_lookup_source (registry, source_id);
- if (!source) {
- GRL_DEBUG ("No source registered with id '%s', ignoring", source_id);
- } else {
- grl_plugin_registry_unregister_source (registry, source, NULL);
- }
-
- g_free (source_id);
-}
-
-const static gchar *
-get_upnp_key (const GrlKeyID key_id)
-{
- return g_hash_table_lookup (key_mapping, key_id);
-}
-
-const static gchar *
-get_upnp_key_for_filter (const GrlKeyID key_id)
-{
- return g_hash_table_lookup (filter_key_mapping, key_id);
-}
-
-static gchar *
-get_upnp_filter (const GList *keys)
-{
- GString *filter;
- GList *iter;
- gchar *upnp_key;
- guint first = TRUE;
-
- filter = g_string_new ("");
- iter = (GList *) keys;
- while (iter) {
- upnp_key =
- (gchar *) get_upnp_key_for_filter (iter->data);
- if (upnp_key) {
- if (!first) {
- g_string_append (filter, ",");
- }
- g_string_append (filter, upnp_key);
- first = FALSE;
- }
- iter = g_list_next (iter);
- }
-
- return g_string_free (filter, FALSE);
-}
-
-static gchar *
-get_upnp_search (const gchar *text)
-{
- if (text) {
- return g_strdup_printf (UPNP_SEARCH_SPEC, text, text, text);
- } else {
- return g_strdup (UPNP_SEARCH_ALL);
- }
-}
-
-static void
-setup_key_mappings (void)
-{
- /* For key_mapping we only have to set mapping for keys that
- are not handled directly with the corresponding fw key
- (see ket_valur_for_key) */
- key_mapping = g_hash_table_new (g_direct_hash, g_direct_equal);
- filter_key_mapping = g_hash_table_new (g_direct_hash, g_direct_equal);
-
- g_hash_table_insert (key_mapping, GRL_METADATA_KEY_TITLE, "title");
- g_hash_table_insert (key_mapping, GRL_METADATA_KEY_ARTIST, "artist");
- g_hash_table_insert (key_mapping, GRL_METADATA_KEY_ALBUM, "album");
- g_hash_table_insert (key_mapping, GRL_METADATA_KEY_GENRE, "genre");
- g_hash_table_insert (key_mapping, GRL_METADATA_KEY_URL, "res");
- g_hash_table_insert (key_mapping, GRL_METADATA_KEY_DATE, "modified");
- g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_TITLE, "title");
- g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_URL, "res");
- g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_DATE, "modified");
- g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_ARTIST, "upnp:artist");
- g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_ALBUM, "upnp:album");
- g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_GENRE, "upnp:genre");
- g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_DURATION, "res@duration");
- g_hash_table_insert (filter_key_mapping, GRL_METADATA_KEY_DATE, "modified");
-}
-
-static gchar *
-didl_res_get_protocol_info (xmlNode* res_node, gint field)
-{
- gchar* pinfo;
- gchar* value;
- gchar** array;
-
- pinfo = (gchar *) xmlGetProp (res_node, (const xmlChar *) "protocolInfo");
- if (pinfo == NULL) {
- return NULL;
- }
-
- /* 0:protocol, 1:network, 2:mime-type and 3:additional info. */
- array = g_strsplit (pinfo, ":", 4);
- g_free(pinfo);
- if (g_strv_length (array) < 4) {
- value = NULL;
- } else {
- value = g_strdup (array[field]);
- }
-
- g_strfreev (array);
-
- return value;
-}
-
-static GList *
-didl_get_supported_resources (GUPnPDIDLLiteObject *didl)
-{
- GList *properties, *node;
- xmlNode *xml_node;
- gchar *protocol;
-
- properties = gupnp_didl_lite_object_get_properties (didl, "res");
-
- node = properties;
- while (node) {
- xml_node = (xmlNode *) node->data;
- if (!xml_node) {
- node = properties = g_list_delete_link (properties, node);
- continue;
- }
-
- protocol = didl_res_get_protocol_info (xml_node, 0);
- if (protocol && strcmp (protocol, "http-get") != 0) {
- node = properties = g_list_delete_link (properties, node);
- g_free (protocol);
- continue;
- }
- g_free (protocol);
- node = g_list_next (node);
- }
-
- return properties;
-}
-
-static gint
-didl_h_mm_ss_to_int (const gchar *time)
-{
- guint len = 0;
- guint i = 0;
- guint head = 0;
- guint tail = 0;
- int result = 0;
- gchar* tmp = NULL;
- gboolean has_hours = FALSE;
-
- if (!time) {
- return -1;
- }
-
- len = strlen (time);
- tmp = g_new0 (gchar, sizeof (gchar) * len);
-
- /* Find the first colon (it can be anywhere) and also count the
- * amount of colons to know if there are hours or not */
- for (i = 0; i < len; i++) {
- if (time[i] == ':') {
- if (tail != 0) {
- has_hours = TRUE;
- } else {
- tail = i;
- }
- }
- }
-
- if (tail > len || head > tail) {
- g_free (tmp);
- return -1;
- }
-
- /* Hours */
- if (has_hours == TRUE) {
- memcpy (tmp, time + head, tail - head);
- tmp[tail - head + 1] = '\0';
- result += 3600 * atoi (tmp);
- /* The next colon should be exactly 2 chars right */
- head = tail + 1;
- tail = head + 2;
- } else {
- /* The format is now exactly MM:SS */
- head = 0;
- tail = 2;
- }
-
- /* Bail out if tail goes too far or head is bigger than tail */
- if (tail > len || head > tail) {
- g_free (tmp);
- return -1;
- }
-
- /* Minutes */
- memcpy (tmp, time + head, tail - head);
- tmp[2] = '\0';
- result += 60 * atoi (tmp);
-
- /* The next colon should again be exactly 2 chars right */
- head = tail + 1;
- tail = head + 2;
-
- /* Bail out if tail goes too far or head is bigger than tail */
- if (tail > len || head > tail) {
- g_free (tmp);
- return -1;
- }
-
- /* Extract seconds */
- memcpy (tmp, time + head, tail - head);
- tmp[2] = '\0';
- result += atoi(tmp);
- g_free (tmp);
-
- return result;
-
-}
-
-static gboolean
-is_image (xmlNode *node)
-{
- gchar *mime_type;
- gboolean ret;
-
- mime_type = didl_res_get_protocol_info (node, 2);
- ret = g_str_has_prefix (mime_type, "image/");
-
- g_free (mime_type);
- return ret;
-}
-
-static gboolean
-is_http_get (xmlNode *node)
-{
- gboolean ret;
- gchar *protocol;
-
- protocol = didl_res_get_protocol_info (node, 0);
- ret = g_str_has_prefix (protocol, "http-get");
-
- g_free (protocol);
- return ret;
-}
-
-static gboolean
-has_thumbnail_marker (xmlNode *node)
-{
- gchar *dlna_stuff;
- gboolean ret;
-
- dlna_stuff = didl_res_get_protocol_info (node, 3);
- ret = strstr("JPEG_TN", dlna_stuff) != NULL;
-
- g_free (dlna_stuff);
- return ret;
-}
-
-static gchar *
-get_thumbnail (GList *nodes)
-{
- GList *element;
- gchar *val = NULL;
- guint counter = 0;
-
- /* chose, depending on availability, the first with DLNA.ORG_PN=JPEG_TN, or
- * the last http-get with mimetype image/something if there is more than one
- * http-get.
- * This covers at least mediatomb and rygel.
- * This could be improved by handling resolution and/or size */
-
- for (element=nodes; element; element=g_list_next (element)) {
- xmlNode *node = (xmlNode *)element->data;
-
- if (is_http_get (node)) {
- counter++;
- if (is_image (node)) {
- if (val)
- g_free (val);
- val = (gchar *) xmlNodeGetContent (node);
-
- if (has_thumbnail_marker (node)) /* that's definitely it! */
- return val;
- }
- }
- }
-
- if (val && counter == 1) {
- /* There was only one element with http-get protocol: that's the uri of the
- * media itself, not a thumbnail */
- g_free (val);
- val = NULL;
- }
-
- return val;
-}
-
-static gchar *
-get_value_for_key (GrlKeyID key_id,
- GUPnPDIDLLiteObject *didl,
- GList *props)
-{
- GList* list;
- gchar* val = NULL;
- const gchar* upnp_key;
-
- xmlNode *didl_node = gupnp_didl_lite_object_get_xml_node (didl);
- upnp_key = get_upnp_key (key_id);
-
- if (key_id == GRL_METADATA_KEY_CHILDCOUNT) {
- val = (gchar *) xmlGetProp (didl_node,
- (const xmlChar *) "childCount");
- } else if (key_id == GRL_METADATA_KEY_MIME && props) {
- val = didl_res_get_protocol_info ((xmlNode *) props->data, 2);
- } else if (key_id == GRL_METADATA_KEY_DURATION && props) {
- val = (gchar *) xmlGetProp ((xmlNodePtr) props->data,
- (const xmlChar *) "duration");
- } else if (key_id == GRL_METADATA_KEY_URL && props) {
- val = (gchar *) xmlNodeGetContent ((xmlNode *) props->data);
- } else if (key_id == GRL_METADATA_KEY_THUMBNAIL && props) {
- val = g_strdup (gupnp_didl_lite_object_get_album_art (didl));
- if (!val)
- val = get_thumbnail (props);
- } else if (upnp_key) {
- list = gupnp_didl_lite_object_get_properties (didl, upnp_key);
- if (list) {
- val = (gchar *) xmlNodeGetContent ((xmlNode*) list->data);
- g_list_free (list);
- } else if (props && props->data) {
- val = (gchar *) xmlGetProp ((xmlNodePtr) props->data,
- (const xmlChar *) upnp_key);
- }
- }
-
- return val;
-}
-
-static void
-set_metadata_value (GrlData *data,
- GrlKeyID key_id,
- const gchar *value)
-{
- if (key_id == GRL_METADATA_KEY_DURATION) {
- gint duration = didl_h_mm_ss_to_int (value);
- if (duration >= 0) {
- grl_data_set_int (data, GRL_METADATA_KEY_DURATION, duration);
- }
- } else if (key_id == GRL_METADATA_KEY_CHILDCOUNT && value) {
- grl_data_set_int (data, GRL_METADATA_KEY_CHILDCOUNT, atoi (value));
- } else {
- grl_data_set_string (data, key_id, value);
- }
-}
-
-static GrlMedia *
-build_media_from_didl (GrlMedia *content,
- GUPnPDIDLLiteObject *didl_node,
- GList *keys)
-{
- const gchar *id;
- const gchar *class;
-
- GrlMedia *media = NULL;
- GList *didl_props;
- GList *iter;
-
- GRL_DEBUG ("build_media_from_didl");
-
- if (content) {
- media = content;
- } else {
-
- if (GUPNP_IS_DIDL_LITE_CONTAINER (didl_node)) {
- media = grl_media_box_new ();
- } else {
- if (!media) {
- class = gupnp_didl_lite_object_get_upnp_class (didl_node);
- if (class) {
- if (g_str_has_prefix (class, "object.item.audioItem")) {
- media = grl_media_audio_new ();
- } else if (g_str_has_prefix (class, "object.item.videoItem")) {
- media = grl_media_video_new ();
- } else if (g_str_has_prefix (class, "object.item.imageItem")) {
- media = grl_media_image_new ();
- } else {
- media = grl_media_new ();
- }
- } else {
- media = grl_media_new ();
- }
- }
- }
- }
-
- id = gupnp_didl_lite_object_get_id (didl_node);
- /* Root category's id is always NULL */
- if (g_strcmp0 (id, "0") == 0) {
- grl_media_set_id (media, NULL);
- } else {
- grl_media_set_id (media, id);
- }
-
- didl_props = didl_get_supported_resources (didl_node);
-
- iter = keys;
- while (iter) {
- gchar *value = get_value_for_key (iter->data, didl_node, didl_props);
- if (value) {
- set_metadata_value (GRL_DATA (media), iter->data, value);
- }
- iter = g_list_next (iter);
- }
-
- g_list_free (didl_props);
-
- return media;
-}
-
-static void
-gupnp_browse_result_cb (GUPnPDIDLLiteParser *parser,
- GUPnPDIDLLiteObject *didl,
- gpointer user_data)
-{
- GrlMedia *media;
- struct OperationSpec *os = (struct OperationSpec *) user_data;
- if (gupnp_didl_lite_object_get_id (didl)) {
- media = build_media_from_didl (NULL, didl, os->keys);
- os->callback (os->source,
- os->operation_id,
- media,
- --os->count,
- os->user_data,
- NULL);
- }
-}
-
-static void
-gupnp_browse_cb (GUPnPServiceProxy *service,
- GUPnPServiceProxyAction *action,
- gpointer user_data)
-{
- GError *error = NULL;
- gchar *didl = NULL;
- guint returned = 0;
- guint matches = 0;
- gboolean result;
- struct OperationSpec *os;
- GUPnPDIDLLiteParser *didl_parser;
-
- GRL_DEBUG ("gupnp_browse_cb");
-
- os = (struct OperationSpec *) user_data;
- didl_parser = gupnp_didl_lite_parser_new ();
-
- result =
- gupnp_service_proxy_end_action (service, action, &error,
- "Result", G_TYPE_STRING, &didl,
- "NumberReturned", G_TYPE_UINT, &returned,
- "TotalMatches", G_TYPE_UINT, &matches,
- NULL);
-
- if (!result) {
- GRL_WARNING ("Operation (browse, search or query) failed");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- if (error) {
- GRL_WARNING (" Reason: %s", error->message);
- g_error_free (error);
- }
-
- goto free_resources;
- }
-
- if (!didl || !returned) {
- GRL_DEBUG ("Got no results");
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
-
- goto free_resources;
- }
-
- /* Use os->count to emit "remaining" information */
- if (os->count > returned) {
- os->count = returned;
- }
-
- g_signal_connect (G_OBJECT (didl_parser),
- "object-available",
- G_CALLBACK (gupnp_browse_result_cb),
- os);
- gupnp_didl_lite_parser_parse_didl (didl_parser,
- didl,
- &error);
- if (error) {
- GRL_WARNING ("Failed to parse DIDL result: %s", error->message);
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
-
- goto free_resources;
- }
-
- free_resources:
- g_slice_free (struct OperationSpec, os);
- g_free (didl);
- g_object_unref (didl_parser);
-}
-
-static void
-gupnp_metadata_result_cb (GUPnPDIDLLiteParser *parser,
- GUPnPDIDLLiteObject *didl,
- gpointer user_data)
-{
- GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
- if (gupnp_didl_lite_object_get_id (didl)) {
- build_media_from_didl (ms->media, didl, ms->keys);
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- }
-}
-
-static void
-gupnp_metadata_cb (GUPnPServiceProxy *service,
- GUPnPServiceProxyAction *action,
- gpointer user_data)
-{
- GError *error = NULL;
- gchar *didl = NULL;
- gboolean result;
- GrlMediaSourceMetadataSpec *ms;
- GUPnPDIDLLiteParser *didl_parser;
-
- GRL_DEBUG ("gupnp_metadata_cb");
-
- ms = (GrlMediaSourceMetadataSpec *) user_data;
- didl_parser = gupnp_didl_lite_parser_new ();
-
- result =
- gupnp_service_proxy_end_action (service, action, &error,
- "Result", G_TYPE_STRING, &didl,
- NULL);
-
- if (!result) {
- GRL_WARNING ("Metadata operation failed");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- if (error) {
- GRL_WARNING (" Reason: %s", error->message);
- g_error_free (error);
- }
-
- goto free_resources;
- }
-
- if (!didl) {
- GRL_DEBUG ("Got no metadata");
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
-
- goto free_resources;
- }
-
- g_signal_connect (G_OBJECT (didl_parser),
- "object-available",
- G_CALLBACK (gupnp_metadata_result_cb),
- ms);
- gupnp_didl_lite_parser_parse_didl (didl_parser,
- didl,
- &error);
- if (error) {
- GRL_WARNING ("Failed to parse DIDL result: %s", error->message);
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- goto free_resources;
- }
-
- free_resources:
- g_free (didl);
- g_object_unref (didl_parser);
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_upnp_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_URL,
- GRL_METADATA_KEY_MIME,
- GRL_METADATA_KEY_DATE,
- GRL_METADATA_KEY_DURATION,
- GRL_METADATA_KEY_ARTIST,
- GRL_METADATA_KEY_ALBUM,
- GRL_METADATA_KEY_GENRE,
- GRL_METADATA_KEY_CHILDCOUNT,
- GRL_METADATA_KEY_THUMBNAIL,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_upnp_source_browse (GrlMediaSource *source, GrlMediaSourceBrowseSpec *bs)
-{
- GUPnPServiceProxyAction* action;
- gchar *upnp_filter;
- gchar *container_id;
- GError *error = NULL;
- struct OperationSpec *os;
-
- GRL_DEBUG ("grl_upnp_source_browse");
-
- upnp_filter = get_upnp_filter (bs->keys);
- GRL_DEBUG ("filter: '%s'", upnp_filter);
-
- os = g_slice_new0 (struct OperationSpec);
- os->source = bs->source;
- os->operation_id = bs->browse_id;
- os->keys = bs->keys;
- os->skip = bs->skip;
- os->count = bs->count;
- os->callback = bs->callback;
- os->user_data = bs->user_data;
-
- container_id = (gchar *) grl_media_get_id (bs->container);
- if (!container_id) {
- container_id = "0";
- }
-
- action =
- gupnp_service_proxy_begin_action (GRL_UPNP_SOURCE (source)->priv->service,
- "Browse", gupnp_browse_cb,
- os,
- "ObjectID", G_TYPE_STRING,
- container_id,
- "BrowseFlag", G_TYPE_STRING,
- "BrowseDirectChildren",
- "Filter", G_TYPE_STRING,
- upnp_filter,
- "StartingIndex", G_TYPE_UINT,
- bs->skip,
- "RequestedCount", G_TYPE_UINT,
- bs->count,
- "SortCriteria", G_TYPE_STRING,
- "",
- NULL);
- if (!action) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Failed to start browse action");
- bs->callback (bs->source, bs->browse_id, NULL, 0, bs->user_data, error);
- g_error_free (error);
- g_slice_free (struct OperationSpec, os);
- }
-
- g_free (upnp_filter);
-}
-
-static void
-grl_upnp_source_search (GrlMediaSource *source, GrlMediaSourceSearchSpec *ss)
-{
- GUPnPServiceProxyAction* action;
- gchar *upnp_filter;
- GError *error = NULL;
- gchar *upnp_search;
- struct OperationSpec *os;
-
- GRL_DEBUG ("grl_upnp_source_search");
-
- upnp_filter = get_upnp_filter (ss->keys);
- GRL_DEBUG ("filter: '%s'", upnp_filter);
-
- upnp_search = get_upnp_search (ss->text);
- GRL_DEBUG ("search: '%s'", upnp_search);
-
- os = g_slice_new0 (struct OperationSpec);
- os->source = ss->source;
- os->operation_id = ss->search_id;
- os->keys = ss->keys;
- os->skip = ss->skip;
- os->count = ss->count;
- os->callback = ss->callback;
- os->user_data = ss->user_data;
-
- action =
- gupnp_service_proxy_begin_action (GRL_UPNP_SOURCE (source)->priv->service,
- "Search", gupnp_browse_cb,
- os,
- "ContainerID", G_TYPE_STRING,
- "0",
- "SearchCriteria", G_TYPE_STRING,
- upnp_search,
- "Filter", G_TYPE_STRING,
- upnp_filter,
- "StartingIndex", G_TYPE_UINT,
- ss->skip,
- "RequestedCount", G_TYPE_UINT,
- ss->count,
- "SortCriteria", G_TYPE_STRING,
- "",
- NULL);
- if (!action) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_SEARCH_FAILED,
- "Failed to start browse action");
- ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, error);
- g_error_free (error);
- g_slice_free (struct OperationSpec, os);
- }
-
- g_free (upnp_filter);
- g_free (upnp_search);
-}
-
-/*
- * Query format is the UPnP ContentDirectory SearchCriteria format, e.g.
- * 'upnp:artist contains "Rick Astley" and
- * (upnp:class derivedfrom "object.item.audioItem")'
- *
- * Note that we don't guarantee or check that the server actually
- * supports the given criteria. Offering the searchcaps as
- * additional metadata to clients that _really_ are interested might
- * be useful.
- */
-static void
-grl_upnp_source_query (GrlMediaSource *source, GrlMediaSourceQuerySpec *qs)
-{
- GUPnPServiceProxyAction* action;
- gchar *upnp_filter;
- GError *error = NULL;
- struct OperationSpec *os;
-
- GRL_DEBUG (__func__);
-
- upnp_filter = get_upnp_filter (qs->keys);
- GRL_DEBUG ("filter: '%s'", upnp_filter);
-
- GRL_DEBUG ("query: '%s'", qs->query);
-
- os = g_slice_new0 (struct OperationSpec);
- os->source = qs->source;
- os->operation_id = qs->query_id;
- os->keys = qs->keys;
- os->skip = qs->skip;
- os->count = qs->count;
- os->callback = qs->callback;
- os->user_data = qs->user_data;
-
- action =
- gupnp_service_proxy_begin_action (GRL_UPNP_SOURCE (source)->priv->service,
- "Search", gupnp_browse_cb, os,
- "ContainerID", G_TYPE_STRING,
- "0",
- "SearchCriteria", G_TYPE_STRING,
- qs->query,
- "Filter", G_TYPE_STRING,
- upnp_filter,
- "StartingIndex", G_TYPE_UINT,
- qs->skip,
- "RequestedCount", G_TYPE_UINT,
- qs->count,
- "SortCriteria", G_TYPE_STRING,
- "",
- NULL);
- if (!action) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_QUERY_FAILED,
- "Failed to start query action");
- qs->callback (qs->source, qs->query_id, NULL, 0, qs->user_data, error);
- g_error_free (error);
- g_slice_free (struct OperationSpec, os);
- }
-
- g_free (upnp_filter);
-}
-
-static void
-grl_upnp_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- GUPnPServiceProxyAction* action;
- gchar *upnp_filter;
- gchar *id;
- GError *error = NULL;
-
- GRL_DEBUG ("grl_upnp_source_metadata");
-
- upnp_filter = get_upnp_filter (ms->keys);
-
- GRL_DEBUG ("filter: '%s'", upnp_filter);
-
- id = (gchar *) grl_media_get_id (ms->media);
- if (!id) {
- grl_media_set_title (ms->media, GRL_UPNP_SOURCE (source)->priv->upnp_name);
- id = "0";
- }
-
- action =
- gupnp_service_proxy_begin_action (GRL_UPNP_SOURCE (source)->priv->service,
- "Browse", gupnp_metadata_cb,
- ms,
- "ObjectID", G_TYPE_STRING,
- id,
- "BrowseFlag", G_TYPE_STRING,
- "BrowseMetadata",
- "Filter", G_TYPE_STRING,
- upnp_filter,
- "StartingIndex", G_TYPE_UINT,
- 0,
- "RequestedCount", G_TYPE_UINT,
- 0,
- "SortCriteria", G_TYPE_STRING,
- "",
- NULL);
- if (!action) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Failed to start metadata action");
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- }
-
- g_free (upnp_filter);
-}
-
-static GrlSupportedOps
-grl_upnp_source_supported_operations (GrlMetadataSource *metadata_source)
-{
- GrlSupportedOps caps;
- GrlUpnpSource *source;
-
- /* Some sources may support search() while other not, so we rewrite
- supported_operations() to take that into account.
- See also note in grl_upnp_source_query() */
-
- source = GRL_UPNP_SOURCE (metadata_source);
- caps = GRL_OP_BROWSE | GRL_OP_METADATA | GRL_OP_NOTIFY_CHANGE;
- if (source->priv->search_enabled)
- caps = caps | GRL_OP_SEARCH | GRL_OP_QUERY;
-
- return caps;
-}
-
-static gboolean
-grl_upnp_source_notify_change_start (GrlMediaSource *source,
- GError **error)
-{
- GrlUpnpSource *upnp_source = GRL_UPNP_SOURCE (source);
-
- if (!gupnp_service_proxy_add_notify (upnp_source->priv->service,
- "ContainerUpdateIDs",
- G_TYPE_STRING,
- container_changed_cb,
- source)) {
- g_set_error (error,
- GRL_CORE_ERROR,
- GRL_CORE_ERROR_NOTIFY_CHANGED_FAILED,
- "Unable to listen for changes in %s",
- grl_metadata_source_get_id (GRL_METADATA_SOURCE (source)));
- return FALSE;
- }
- gupnp_service_proxy_set_subscribed (upnp_source->priv->service, TRUE);
-
- return TRUE;
-}
-
-
-static gboolean
-grl_upnp_source_notify_change_stop (GrlMediaSource *source,
- GError **error)
-{
- GrlUpnpSource *upnp_source = GRL_UPNP_SOURCE (source);
-
- gupnp_service_proxy_set_subscribed (upnp_source->priv->service, FALSE);
- gupnp_service_proxy_remove_notify (upnp_source->priv->service,
- "ContainerUpdateIDs",
- container_changed_cb,
- source);
-
- return TRUE;
-}
diff --git a/src/upnp/grl-upnp.h b/src/upnp/grl-upnp.h
deleted file mode 100644
index e0bc748..0000000
--- a/src/upnp/grl-upnp.h
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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_UPNP_SOURCE_H_
-#define _GRL_UPNP_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_UPNP_SOURCE_TYPE \
- (grl_upnp_source_get_type ())
-
-#define GRL_UPNP_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_UPNP_SOURCE_TYPE, \
- GrlUpnpSource))
-
-#define GRL_IS_UPNP_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_UPNP_SOURCE_TYPE))
-
-#define GRL_UPNP_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_UPNP_SOURCE_TYPE, \
- GrlUpnpSourceClass))
-
-#define GRL_IS_UPNP_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_UPNP_SOURCE_TYPE))
-
-#define GRL_UPNP_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_UPNP_SOURCE_TYPE, \
- GrlUpnpSourceClass))
-
-typedef struct _GrlUpnpPrivate GrlUpnpPrivate;
-typedef struct _GrlUpnpSource GrlUpnpSource;
-
-struct _GrlUpnpSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlUpnpPrivate *priv;
-};
-
-typedef struct _GrlUpnpSourceClass GrlUpnpSourceClass;
-
-struct _GrlUpnpSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_upnp_source_get_type (void);
-
-#endif /* _GRL_UPNP_SOURCE_H_ */
diff --git a/src/upnp/grl-upnp.xml b/src/upnp/grl-upnp.xml
deleted file mode 100644
index 3617b98..0000000
--- a/src/upnp/grl-upnp.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>UPnP</name>
- <description>A plugin for browsing UPnP servers</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/vimeo/Makefile.am b/src/vimeo/Makefile.am
deleted file mode 100644
index b810218..0000000
--- a/src/vimeo/Makefile.am
+++ /dev/null
@@ -1,42 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Joaquim Rocha <jrocha igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlvimeo.la
-
-libgrlvimeo_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(XML_CFLAGS) \
- $(GTHREAD_CFLAGS) \
- $(LIBSOUP_CFLAGS)
-
-libgrlvimeo_la_LIBADD = \
- $(DEPS_LIBS) \
- $(XML_LIBS) \
- $(GTHREAD_LIBS) \
- $(LIBSOUP_LIBS)
-
-libgrlvimeo_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlvimeo_la_SOURCES = \
- grl-vimeo.c \
- grl-vimeo.h \
- gvimeo.c \
- gvimeo.h
-
-libdir=$(GRL_PLUGINS_DIR)
-vimeoxmldir = $(GRL_PLUGINS_CONF_DIR)
-vimeoxml_DATA = $(VIMEO_PLUGIN_ID).xml
-
-EXTRA_DIST = $(vimeoxml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/vimeo/grl-vimeo.c b/src/vimeo/grl-vimeo.c
deleted file mode 100644
index a235217..0000000
--- a/src/vimeo/grl-vimeo.c
+++ /dev/null
@@ -1,413 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- * Copyright (C) 2011 Intel Corporation.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Joaquim Rocha <jrocha igalia 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 <grilo.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
-
-#include "grl-vimeo.h"
-#include "gvimeo.h"
-
-#define GRL_VIMEO_SOURCE_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_VIMEO_SOURCE_TYPE, \
- GrlVimeoSourcePrivate))
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT vimeo_log_domain
-GRL_LOG_DOMAIN_STATIC(vimeo_log_domain);
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID VIMEO_PLUGIN_ID
-
-#define SOURCE_ID "grl-vimeo"
-#define SOURCE_NAME "Vimeo"
-#define SOURCE_DESC "A source for browsing and searching Vimeo videos"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-typedef struct {
- GrlMediaSourceSearchSpec *ss;
- gint offset;
- gint page;
-} SearchData;
-
-struct _GrlVimeoSourcePrivate {
- GVimeo *vimeo;
-};
-
-static GrlVimeoSource *grl_vimeo_source_new (void);
-
-gboolean grl_vimeo_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-static const GList *grl_vimeo_source_supported_keys (GrlMetadataSource *source);
-
-static void grl_vimeo_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ss);
-
-static void grl_vimeo_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-
-/* =================== Vimeo Plugin =============== */
-
-gboolean
-grl_vimeo_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- gchar *vimeo_key;
- gchar *vimeo_secret;
- GrlConfig *config;
- gint config_count;
- gboolean init_result = FALSE;
- GrlVimeoSource *source;
-
- GRL_LOG_DOMAIN_INIT (vimeo_log_domain, "vimeo");
-
- GRL_DEBUG ("vimeo_plugin_init");
-
- if (!g_thread_supported ()) {
- g_thread_init (NULL);
- }
-
- if (!configs) {
- GRL_WARNING ("Missing configuration");
- return FALSE;
- }
-
- config_count = g_list_length (configs);
- if (config_count > 1) {
- GRL_WARNING ("Provided %d configs, but will only use one", config_count);
- }
-
- config = GRL_CONFIG (configs->data);
-
- vimeo_key = grl_config_get_api_key (config);
- vimeo_secret = grl_config_get_api_secret (config);
-
- if (!vimeo_key || !vimeo_secret) {
- GRL_WARNING ("Required configuration keys not set up");
- goto go_out;
- }
-
- source = grl_vimeo_source_new ();
- source->priv->vimeo = g_vimeo_new (vimeo_key, vimeo_secret);
-
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
- init_result = TRUE;
-
- go_out:
-
- if (vimeo_key != NULL)
- g_free (vimeo_key);
- if (vimeo_secret != NULL)
- g_free (vimeo_secret);
-
- return init_result;
-}
-
-GRL_PLUGIN_REGISTER (grl_vimeo_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Vimeo GObject ================ */
-
-static GrlVimeoSource *
-grl_vimeo_source_new (void)
-{
- GRL_DEBUG ("grl_vimeo_source_new");
-
- return g_object_new (GRL_VIMEO_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- NULL);
-}
-
-static void
-grl_vimeo_source_class_init (GrlVimeoSourceClass * klass)
-{
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
-
- source_class->metadata = grl_vimeo_source_metadata;
- source_class->search = grl_vimeo_source_search;
- metadata_class->supported_keys = grl_vimeo_source_supported_keys;
-
- g_type_class_add_private (klass, sizeof (GrlVimeoSourcePrivate));
-}
-
-static void
-grl_vimeo_source_init (GrlVimeoSource *source)
-{
- source->priv = GRL_VIMEO_SOURCE_GET_PRIVATE (source);
-}
-
-G_DEFINE_TYPE (GrlVimeoSource, grl_vimeo_source, GRL_TYPE_MEDIA_SOURCE);
-
-/* ======================= Utilities ==================== */
-
-static gint
-str_to_gint (gchar *str)
-{
- gint number;
-
- errno = 0;
- number = (gint) g_ascii_strtod (str, NULL);
- if (errno == 0)
- {
- return number;
- }
- return 0;
-}
-
-static void
-update_media (GrlMedia *media, GHashTable *video)
-{
- gchar *str;
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_ID);
- if (str)
- {
- grl_media_set_id (media, str);
- }
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_TITLE);
- if (str)
- {
- grl_media_set_title (media, str);
- }
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_DESCRIPTION);
- if (str)
- {
- grl_media_set_description (media, str);
- }
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_DURATION);
- if (str)
- {
- grl_media_set_duration (media, str_to_gint (str));
- }
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_OWNER_NAME);
- if (str)
- {
- grl_media_set_author (media, str);
- }
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_UPLOAD_DATE);
- if (str)
- {
- grl_media_set_date (media, str);
- }
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_THUMBNAIL);
- if (str)
- {
- grl_media_set_thumbnail (media, str);
- }
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_WIDTH);
- if (str)
- {
- grl_media_video_set_width (GRL_MEDIA_VIDEO (media), str_to_gint (str));
- }
-
- str = g_hash_table_lookup (video, VIMEO_VIDEO_HEIGHT);
- if (str)
- {
- grl_media_video_set_height (GRL_MEDIA_VIDEO (media), str_to_gint (str));
- }
-}
-
-
-static void
-search_cb (GVimeo *vimeo, GList *video_list, gpointer user_data)
-{
- GrlMedia *media = NULL;
- SearchData *sd = (SearchData *) user_data;
- gint count = sd->ss->count;
- gchar *media_type;
-
- /* Go to offset element */
- video_list = g_list_nth (video_list, sd->offset);
-
- /* No more elements can be sent */
- if (!video_list) {
- sd->ss->callback (sd->ss->source,
- sd->ss->search_id,
- NULL,
- 0,
- sd->ss->user_data,
- NULL);
- g_slice_free (SearchData, sd);
- return;
- }
-
- while (video_list && count)
- {
- media_type = g_hash_table_lookup (video_list->data, "title");
- if (media_type) {
- media = grl_media_video_new ();
- }
-
- if (media)
- {
- update_media (media, video_list->data);
- sd->ss->callback (sd->ss->source,
- sd->ss->search_id,
- media,
- sd->ss->count == 1? 0: -1,
- sd->ss->user_data,
- NULL);
- }
- video_list = g_list_next (video_list);
-
- if (--count)
- sd->ss->count = count;
-
- media = NULL;
- }
-
- /* Get more elements */
- if (count)
- {
- sd->offset = 0;
- sd->page++;
- g_vimeo_videos_search (vimeo, sd->ss->text, sd->page, search_cb, sd);
- }
- else
- {
- g_slice_free (SearchData, sd);
- }
-}
-
-static void
-video_get_play_url_cb (gchar *url, gpointer user_data)
-{
- GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
-
- if (url)
- {
- grl_media_set_url (ms->media, url);
- }
-
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_vimeo_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_DESCRIPTION,
- GRL_METADATA_KEY_URL,
- GRL_METADATA_KEY_AUTHOR,
- GRL_METADATA_KEY_DATE,
- GRL_METADATA_KEY_THUMBNAIL,
- GRL_METADATA_KEY_DURATION,
- GRL_METADATA_KEY_WIDTH,
- GRL_METADATA_KEY_HEIGHT,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_vimeo_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- gint id;
- const gchar *id_str;
-
- if (!ms->media || (id_str = grl_media_get_id (ms->media)) == NULL)
- {
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- return;
- }
-
- errno = 0;
- id = (gint) g_ascii_strtod (id_str, NULL);
- if (errno != 0)
- {
- return;
- }
-
- g_vimeo_video_get_play_url (GRL_VIMEO_SOURCE (source)->priv->vimeo,
- id,
- video_get_play_url_cb,
- ms);
-}
-
-static void
-grl_vimeo_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss)
-{
- SearchData *sd;
- GError *error;
- gint per_page;
- GVimeo *vimeo = GRL_VIMEO_SOURCE (source)->priv->vimeo;
-
- if (!ss->text) {
- /* Vimeo does not support searching all */
- error =
- g_error_new_literal (GRL_CORE_ERROR,
- GRL_CORE_ERROR_SEARCH_NULL_UNSUPPORTED,
- "Unable to execute search: non NULL search text is required");
- ss->callback (ss->source, ss->search_id, NULL, 0, ss->user_data, error);
- g_error_free (error);
- return;
- }
-
- /* Compute items per page and page offset */
- per_page = CLAMP (1 + ss->skip + ss->count, 0, 100);
- g_vimeo_set_per_page (vimeo, per_page);
-
- sd = g_slice_new (SearchData);
- sd->page = 1 + (ss->skip / per_page);
- sd->offset = ss->skip % per_page;
- sd->ss = ss;
-
- g_vimeo_videos_search (vimeo, ss->text, sd->page, search_cb, sd);
-}
diff --git a/src/vimeo/grl-vimeo.h b/src/vimeo/grl-vimeo.h
deleted file mode 100644
index fde4f36..0000000
--- a/src/vimeo/grl-vimeo.h
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Joaquim Rocha <jrocha igalia 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_VIMEO_SOURCE_H_
-#define _GRL_VIMEO_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_VIMEO_SOURCE_TYPE \
- (grl_vimeo_source_get_type ())
-
-#define GRL_VIMEO_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_VIMEO_SOURCE_TYPE, \
- GrlVimeoSource))
-
-#define GRL_IS_VIMEO_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_VIMEO_SOURCE_TYPE))
-
-#define GRL_VIMEO_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_VIMEO_SOURCE_TYPE, \
- GrlVimeoSourceClass))
-
-#define GRL_IS_VIMEO_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_VIMEO_SOURCE_TYPE))
-
-#define GRL_VIMEO_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_VIMEO_SOURCE_TYPE, \
- GrlVimeoSourceClass))
-
-typedef struct _GrlVimeoSource GrlVimeoSource;
-typedef struct _GrlVimeoSourcePrivate GrlVimeoSourcePrivate;
-
-struct _GrlVimeoSource {
-
- GrlMediaSource parent;
-
- /*< private >*/
- GrlVimeoSourcePrivate *priv;
-};
-
-typedef struct _GrlVimeoSourceClass GrlVimeoSourceClass;
-
-struct _GrlVimeoSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_vimeo_source_get_type (void);
-
-#endif /* _GRL_VIMEO_SOURCE_H_ */
diff --git a/src/vimeo/grl-vimeo.xml b/src/vimeo/grl-vimeo.xml
deleted file mode 100644
index b80c9d9..0000000
--- a/src/vimeo/grl-vimeo.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>Vimeo</name>
- <description>A plugin for searching Vimeo videos</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
diff --git a/src/vimeo/gvimeo.c b/src/vimeo/gvimeo.c
deleted file mode 100644
index 3b7f2fc..0000000
--- a/src/vimeo/gvimeo.c
+++ /dev/null
@@ -1,517 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Joaquim Rocha <jrocha igalia 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
- *
- */
-
-#include <gcrypt.h>
-#include <libsoup/soup.h>
-#include "gvimeo.h"
-#include <libxml/parser.h>
-#include <libxml/xpath.h>
-
-#define G_VIMEO_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- G_VIMEO_TYPE, \
- GVimeoPrivate))
-
-#define PLUGIN_USER_AGENT "Grilo Vimeo Plugin"
-
-#define VIMEO_ENDPOINT "http://vimeo.com/api/rest/v2"
-#define VIMEO_VIDEO_LOAD_URL "http://vimeo.com/moogaloop/load/clip:"
-#define VIMEO_VIDEO_PLAY_URL "http://vimeo.com/moogaloop/play/clip:"
-
-#define VIMEO_VIDEO_SEARCH_METHOD "vimeo.videos.search"
-#define VIMEO_API_OAUTH_SIGN_METHOD "HMAC-SHA1"
-#define VIMEO_API_OAUTH_SIGNATURE_PARAM "&oauth_signature=%s"
-
-#define VIMEO_VIDEO_SEARCH \
- "full_response=yes" \
- "&method=%s" \
- "&oauth_consumer_key=%s" \
- "&oauth_nonce=%s" \
- "&oauth_signature_method=" VIMEO_API_OAUTH_SIGN_METHOD \
- "&oauth_timestamp=%s" \
- "&oauth_token=" \
- "&page=%d" \
- "&per_page=%d" \
- "&query=%s"
-
-typedef struct {
- GVimeo *vimeo;
- GVimeoVideoSearchCb search_cb;
- gpointer user_data;
-} GVimeoVideoSearchData;
-
-typedef struct {
- GVimeo *vimeo;
- gint video_id;
- GVimeoURLCb callback;
- gpointer user_data;
-} GVimeoVideoURLData;
-
-struct _GVimeoPrivate {
- gchar *api_key;
- gchar *auth_token;
- gchar *auth_secret;
- gint per_page;
- SoupSession *async_session;
-};
-
-enum InfoType {SIMPLE, EXTENDED};
-
-typedef struct {
- enum InfoType type;
- gchar *name;
-} VideoInfo;
-
-static VideoInfo video_info[] = {{SIMPLE, VIMEO_VIDEO_TITLE},
- {SIMPLE, VIMEO_VIDEO_DESCRIPTION},
- {SIMPLE, VIMEO_VIDEO_UPLOAD_DATE},
- {SIMPLE, VIMEO_VIDEO_WIDTH},
- {SIMPLE, VIMEO_VIDEO_HEIGHT},
- {SIMPLE, VIMEO_VIDEO_OWNER},
- {SIMPLE, VIMEO_VIDEO_URL},
- {SIMPLE, VIMEO_VIDEO_THUMBNAIL},
- {SIMPLE, VIMEO_VIDEO_DURATION},
- {EXTENDED, VIMEO_VIDEO_OWNER}};
-
-static void g_vimeo_finalize (GObject *object);
-static gchar * encode_uri (const gchar *uri);
-
-/* -------------------- GOBJECT -------------------- */
-
-G_DEFINE_TYPE (GVimeo, g_vimeo, G_TYPE_OBJECT);
-
-static void
-g_vimeo_class_init (GVimeoClass *klass)
-{
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- gobject_class->finalize = g_vimeo_finalize;
-
- g_type_class_add_private (klass, sizeof (GVimeoPrivate));
-}
-
-static void
-g_vimeo_init (GVimeo *vimeo)
-{
- vimeo->priv = G_VIMEO_GET_PRIVATE (vimeo);
- vimeo->priv->per_page = 50;
- vimeo->priv->async_session = soup_session_async_new ();
-}
-
-static void
-g_vimeo_finalize (GObject *object)
-{
- GVimeo *vimeo = G_VIMEO (object);
- g_free (vimeo->priv->api_key);
- g_free (vimeo->priv->auth_secret);
- g_object_unref (vimeo->priv->async_session);
-
- G_OBJECT_CLASS (g_vimeo_parent_class)->finalize (object);
-}
-
-GVimeo *
-g_vimeo_new (const gchar *api_key, const gchar *auth_secret)
-{
- GVimeo *vimeo = g_object_new (G_VIMEO_TYPE, NULL);
- vimeo->priv->api_key = g_strdup (api_key);
- vimeo->priv->auth_secret = g_strdup (auth_secret);
-
- return vimeo;
-}
-
-/* -------------------- PRIVATE API -------------------- */
-
-static gchar *
-get_timestamp (void)
-{
- time_t t = time (NULL);
- return g_strdup_printf ("%d", (gint) t);
-}
-
-static gchar *
-get_nonce (void)
-{
- gchar *timestamp = get_timestamp();
- guint rnd_number = g_random_int ();
- gchar *rnd_str = g_strdup_printf ("%d_%s", rnd_number, timestamp);
- gchar *nonce = g_compute_checksum_for_string (G_CHECKSUM_MD5, rnd_str, -1);
- g_free (timestamp);
- g_free (rnd_str);
-
- return nonce;
-}
-
-static gchar *
-get_videos_search_params (GVimeo *vimeo, const gchar *text, gint page) {
- gchar *encoded_text = encode_uri (text);
- gchar *timestamp = get_timestamp ();
- gchar *nonce = get_nonce ();
- gchar *params = g_strdup_printf (VIMEO_VIDEO_SEARCH,
- VIMEO_VIDEO_SEARCH_METHOD,
- vimeo->priv->api_key,
- nonce,
- timestamp,
- page,
- vimeo->priv->per_page,
- encoded_text);
- g_free (timestamp);
- g_free (nonce);
- g_free (encoded_text);
-
- return params;
-}
-
-static gchar *
-sign_string (gchar *message, gchar *key)
-{
- gchar *signed_message = NULL;
- gcry_md_hd_t digest_obj;
- unsigned char *hmac_digest;
- guint digest_len;
-
- gcry_md_open(&digest_obj,
- GCRY_MD_SHA1,
- GCRY_MD_FLAG_SECURE | GCRY_MD_FLAG_HMAC);
- gcry_md_setkey(digest_obj, key, strlen (key));
- gcry_md_write (digest_obj, message, strlen (message));
- gcry_md_final (digest_obj);
- hmac_digest = gcry_md_read (digest_obj, 0);
-
- digest_len = gcry_md_get_algo_dlen (GCRY_MD_SHA1);
- signed_message = g_base64_encode (hmac_digest, digest_len);
-
- gcry_md_close (digest_obj);
-
- return signed_message;
-}
-
-static gboolean
-result_is_correct (xmlNodePtr node)
-{
- gboolean correct = FALSE;
- xmlChar *stat;
-
- if (xmlStrcmp (node->name, (const xmlChar *) "rsp") == 0)
- {
- stat = xmlGetProp (node, (const xmlChar *) "stat");
- if (stat && xmlStrcmp (stat, (const xmlChar *) "ok") == 0)
- {
- correct = TRUE;
- xmlFree (stat);
- }
- }
-
- return correct;
-}
-
-static void
-add_node (xmlNodePtr node, GHashTable *video)
-{
- xmlAttrPtr attr;
-
- for (attr = node->properties; attr != NULL; attr = attr->next)
- {
- g_hash_table_insert (video,
- g_strconcat ((const gchar *) node->name,
- "_",
- (const gchar *) attr->name,
- NULL),
- (gchar *) xmlGetProp (node, attr->name));
- }
-}
-
-static xmlNodePtr
-xpath_get_node (xmlXPathContextPtr context, gchar *xpath_expr)
-{
- xmlNodePtr node = NULL;
- xmlXPathObjectPtr xpath_obj;
- xpath_obj = xmlXPathEvalExpression ((xmlChar *) xpath_expr, context);
-
- if (xpath_obj && xpath_obj->nodesetval->nodeTab)
- {
- node = xpath_obj->nodesetval->nodeTab[0];
- }
- xmlXPathFreeObject (xpath_obj);
-
- return node;
-}
-
-static gchar *
-get_node_text (xmlXPathContextPtr context, gchar *xpath_expr)
-{
- xmlNodePtr node;
- gchar *node_text = NULL;
-
- node = xpath_get_node (context, xpath_expr);
- if (node)
- {
- node_text = (gchar *) xmlNodeGetContent (node);
- }
-
- return node_text;
-}
-
-static GHashTable *
-get_video (xmlNodePtr node)
-{
- gint i;
- gint array_length;
- xmlXPathContextPtr context;
- gchar *video_id;
- GHashTable *video = g_hash_table_new_full (g_str_hash,
- g_str_equal,
- g_free,
- g_free);
-
- /* Adds the video node's properties */
- add_node (node, video);
-
- context = xmlXPathNewContext (node->doc);
- video_id = (gchar *) xmlGetProp (node, (xmlChar *) "id");
-
- array_length = G_N_ELEMENTS (video_info);
- for (i = 0; i < array_length; i++)
- {
- /* Look for the wanted information only under the current video */
- gchar *xpath_name = g_strdup_printf ("//video[@id=%s]//%s",
- video_id,
- video_info[i].name);
- xmlNodePtr info_node = xpath_get_node (context, xpath_name);
- if (info_node)
- {
- if (video_info[i].type == EXTENDED) {
- add_node (info_node, video);
- }
- else
- {
- g_hash_table_insert (video,
- g_strdup ((const gchar *) info_node->name),
- (gchar *) xmlNodeGetContent (info_node));
- }
- }
- g_free (xpath_name);
- }
- g_free (video_id);
-
- xmlXPathFreeContext (context);
-
- return video;
-}
-
-
-static void
-process_video_search_result (const gchar *xml_result, gpointer user_data)
-{
- xmlDocPtr doc;
- xmlNodePtr node;
- GList *video_list = NULL;
- GVimeoVideoSearchData *data = (GVimeoVideoSearchData *) user_data;
-
- doc = xmlReadMemory (xml_result,
- xmlStrlen ((xmlChar *) xml_result),
- NULL,
- NULL,
- XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- node = xmlDocGetRootElement (doc);
-
- /* Check result is ok */
- if (!node || !result_is_correct (node))
- {
- data->search_cb (data->vimeo, NULL, data->user_data);
- }
- else
- {
- node = node->xmlChildrenNode;
-
- /* Now we're at "video pages" node */
- node = node->xmlChildrenNode;
- while (node)
- {
- video_list = g_list_prepend (video_list, get_video (node));
- node = node->next;
- }
-
- data->search_cb (data->vimeo, g_list_reverse (video_list), data->user_data);
- g_list_foreach (video_list, (GFunc) g_hash_table_unref, NULL);
- g_list_free (video_list);
- }
- g_slice_free (GVimeoVideoSearchData, data);
- xmlFreeDoc (doc);
-}
-
-static void
-search_videos_complete_cb (SoupSession *session,
- SoupMessage *message,
- gpointer *data)
-{
- GVimeoVideoSearchCb *search_data = (GVimeoVideoSearchCb *) data;
- process_video_search_result (message->response_body->data, search_data);
-}
-
-static gchar *
-get_play_url_from_vimeo_xml (const gchar *xml, gint video_id)
-{
- xmlDocPtr doc = xmlRecoverDoc ((xmlChar *) xml);
- xmlXPathContextPtr context = xmlXPathNewContext (doc);
- gchar *request_signature = get_node_text (context,
- "/xml/request_signature[1]");
- gchar *signature_expires = get_node_text (context,
- "/xml/request_signature_expires[1]");
-
- gchar *url = g_strdup_printf ("%s%d/%s/%s/?q=sd",
- VIMEO_VIDEO_PLAY_URL,
- video_id,
- request_signature,
- signature_expires);
-
- g_free (request_signature);
- g_free (signature_expires);
- xmlXPathFreeContext (context);
- xmlFreeDoc (doc);
-
- return url;
-}
-
-static void
-get_video_play_url_complete_cb (SoupSession *session,
- SoupMessage *message,
- gpointer *data)
-{
- GVimeoVideoURLData *url_data;
- gchar *url;
-
- if (message->response_body == NULL)
- {
- return;
- }
-
- url_data = (GVimeoVideoURLData *) data;
- url = get_play_url_from_vimeo_xml (message->response_body->data,
- url_data->video_id);
- url_data->callback (url, url_data->user_data);
- g_slice_free (GVimeoVideoURLData, url_data);
-}
-
-static gchar *
-encode_uri (const gchar *uri)
-{
- return soup_uri_encode (uri, "%!*'();:@&=+$,/?#[] ");
-}
-
-static gchar *
-build_request (GVimeo *vimeo, const gchar *query, gint page)
-{
- gchar *params;
- gchar *endpoint_encoded;
- gchar *key;
- gchar *escaped_str;
- gchar *tmp_str;
- gchar *signature;
-
- g_return_val_if_fail (G_IS_VIMEO (vimeo), NULL);
-
- params = get_videos_search_params (vimeo, query, page);
- endpoint_encoded = encode_uri (VIMEO_ENDPOINT);
- key = g_strdup_printf ("%s&", vimeo->priv->auth_secret);
- escaped_str = encode_uri (params);
- tmp_str = g_strdup_printf ("GET&%s&%s", endpoint_encoded, escaped_str);
- signature = sign_string (tmp_str, key);
- g_free (escaped_str);
- g_free (tmp_str);
- escaped_str = encode_uri (signature);
- tmp_str = g_strdup_printf ("%s?%s" VIMEO_API_OAUTH_SIGNATURE_PARAM,
- VIMEO_ENDPOINT,
- params,
- escaped_str);
-
- g_free (endpoint_encoded);
- g_free (params);
- g_free (key);
- g_free (escaped_str);
- g_free (signature);
-
- return tmp_str;
-}
-
-/* -------------------- PUBLIC API -------------------- */
-
-void
-g_vimeo_set_per_page (GVimeo *vimeo, gint per_page)
-{
- g_return_if_fail (G_IS_VIMEO (vimeo));
- vimeo->priv->per_page = per_page;
-}
-
-void
-g_vimeo_videos_search (GVimeo *vimeo,
- const gchar *text,
- gint page,
- GVimeoVideoSearchCb callback,
- gpointer user_data)
-{
- SoupMessage *message;
- GVimeoVideoSearchData *search_data;
- gchar *request;
-
- g_return_if_fail (G_IS_VIMEO (vimeo));
-
- request = build_request (vimeo, text, page);
- search_data = g_slice_new (GVimeoVideoSearchData);
- search_data->vimeo = vimeo;
- search_data->search_cb = callback;
- search_data->user_data = user_data;
-
- message = soup_message_new ("GET", request);
- soup_session_queue_message (vimeo->priv->async_session,
- message,
- (SoupSessionCallback) search_videos_complete_cb,
- search_data);
- g_free (request);
-}
-
-void
-g_vimeo_video_get_play_url (GVimeo *vimeo,
- gint id,
- GVimeoURLCb callback,
- gpointer user_data)
-{
- GVimeoVideoURLData *data;
- gchar *url = g_strdup_printf ("%s%d",
- VIMEO_VIDEO_LOAD_URL,
- id);
- SoupMessage *message = soup_message_new ("GET", url);
- SoupMessageHeaders *headers = message->request_headers;
- soup_message_headers_append (headers, "User-Agent", PLUGIN_USER_AGENT);
-
- data = g_slice_new (GVimeoVideoURLData);
- data->video_id = id;
- data->vimeo = vimeo;
- data->callback = callback;
- data->user_data = user_data;
-
- soup_session_queue_message (vimeo->priv->async_session,
- message,
- (SoupSessionCallback) get_video_play_url_complete_cb,
- data);
- g_free (url);
-}
diff --git a/src/vimeo/gvimeo.h b/src/vimeo/gvimeo.h
deleted file mode 100644
index 34e17a3..0000000
--- a/src/vimeo/gvimeo.h
+++ /dev/null
@@ -1,112 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia com>
- *
- * Authors: Joaquim Rocha <jrocha igalia 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 _G_VIMEO_H_
-#define _G_VIMEO_H_
-
-#include <glib-object.h>
-
-#define G_VIMEO_TYPE \
- (g_vimeo_get_type ())
-
-#define G_VIMEO(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- G_VIMEO_TYPE, \
- GVimeo))
-
-#define G_IS_VIMEO(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- G_VIMEO_TYPE))
-
-#define G_VIMEO_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- G_VIMEO_TYPE, \
- GVimeoClass))
-
-#define G_IS_VIMEO_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- G_VIMEO_TYPE))
-
-#define G_VIMEO_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- G_VIMEO_TYPE, \
- GVimeoClass))
-
-#define VIMEO_VIDEO "video"
-#define VIMEO_VIDEO_ID VIMEO_VIDEO "_id"
-#define VIMEO_VIDEO_TITLE "title"
-#define VIMEO_VIDEO_DESCRIPTION "description"
-#define VIMEO_VIDEO_URL "url"
-#define VIMEO_VIDEO_UPLOAD_DATE "upload_date"
-#define VIMEO_VIDEO_WIDTH "width"
-#define VIMEO_VIDEO_HEIGHT "height"
-#define VIMEO_VIDEO_DURATION "duration"
-#define VIMEO_VIDEO_OWNER "owner"
-#define VIMEO_VIDEO_THUMBNAIL "thumbnail"
-
-#define VIMEO_VIDEO_OWNER_NAME VIMEO_VIDEO_OWNER "_realname"
-
-typedef struct _GVimeo GVimeo;
-typedef struct _GVimeoPrivate GVimeoPrivate;
-
-struct _GVimeo {
-
- GObject parent;
-
- /*< private >*/
- GVimeoPrivate *priv;
-};
-
-typedef struct _GVimeoClass GVimeoClass;
-
-struct _GVimeoClass {
-
- GObjectClass parent_class;
-
-};
-
-typedef void (*GVimeoVideoSearchCb) (GVimeo *vimeo,
- GList *videolist,
- gpointer user_data);
-
-typedef void (*GVimeoURLCb) (gchar *url, gpointer user_data);
-
-GType g_vimeo_get_type (void);
-
-GVimeo *g_vimeo_new (const gchar *api_key, const gchar *auth_secret);
-
-void g_vimeo_video_get_play_url (GVimeo *vimeo,
- gint id,
- GVimeoURLCb callback,
- gpointer user_data);
-
-void g_vimeo_set_per_page (GVimeo *vimeo, gint per_page);
-
-void g_vimeo_videos_search (GVimeo *vimeo,
- const gchar *text,
- gint page,
- GVimeoVideoSearchCb callback,
- gpointer user_data);
-
-#endif /* _G_VIMEO_H_ */
diff --git a/src/youtube/Makefile.am b/src/youtube/Makefile.am
deleted file mode 100644
index 2384bb8..0000000
--- a/src/youtube/Makefile.am
+++ /dev/null
@@ -1,40 +0,0 @@
-#
-# Makefile.am
-#
-# Author: Iago Toral Quiroga <itoral igalia com>
-#
-# Copyright (C) 2010, 2011 Igalia S.L. All rights reserved.
-
-lib_LTLIBRARIES = libgrlyoutube.la
-
-libgrlyoutube_la_CFLAGS = \
- $(DEPS_CFLAGS) \
- $(GRLNET_CFLAGS) \
- $(XML_CFLAGS) \
- $(GTHREAD_CFLAGS) \
- $(GDATA_CFLAGS)
-
-libgrlyoutube_la_LIBADD = \
- $(DEPS_LIBS) \
- $(GRLNET_LIBS) \
- $(XML_LIBS) \
- $(GTHREAD_LIBS) \
- $(GDATA_LIBS)
-
-libgrlyoutube_la_LDFLAGS = \
- -module \
- -avoid-version
-
-libgrlyoutube_la_SOURCES = grl-youtube.c grl-youtube.h
-
-libdir = $(GRL_PLUGINS_DIR)
-youtubexmldir = $(GRL_PLUGINS_CONF_DIR)
-youtubexml_DATA = $(YOUTUBE_PLUGIN_ID).xml
-
-EXTRA_DIST = $(youtubexml_DATA)
-
-MAINTAINERCLEANFILES = \
- *.in \
- *~
-
-DISTCLEANFILES = $(MAINTAINERCLEANFILES)
diff --git a/src/youtube/TODO b/src/youtube/TODO
deleted file mode 100644
index 409835c..0000000
--- a/src/youtube/TODO
+++ /dev/null
@@ -1,6 +0,0 @@
-- Consider using libgdata.
-- Fix get_metadata() for standard feeds.
-- Consider implementing store() -> video upload.
-- Consider implementing configuration (personal account)
- -> Could add new category for browsing (my videos)
- -> would enable store() and remove() operations.
diff --git a/src/youtube/grl-youtube.c b/src/youtube/grl-youtube.c
deleted file mode 100644
index c6dfd2e..0000000
--- a/src/youtube/grl-youtube.c
+++ /dev/null
@@ -1,1630 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- * Copyright (C) 2011 Intel Corporation.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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 <grilo.h>
-#include <net/grl-net.h>
-#include <gdata/gdata.h>
-#include <string.h>
-
-#include "grl-youtube.h"
-
-enum {
- PROP_0,
- PROP_SERVICE,
-};
-
-#define GRL_YOUTUBE_SOURCE_GET_PRIVATE(object) \
- (G_TYPE_INSTANCE_GET_PRIVATE((object), \
- GRL_YOUTUBE_SOURCE_TYPE, \
- GrlYoutubeSourcePriv))
-
-/* --------- Logging -------- */
-
-#define GRL_LOG_DOMAIN_DEFAULT youtube_log_domain
-GRL_LOG_DOMAIN_STATIC(youtube_log_domain);
-
-/* ----- Root categories ---- */
-
-#define YOUTUBE_ROOT_NAME "Youtube"
-
-#define ROOT_DIR_FEEDS_INDEX 0
-#define ROOT_DIR_CATEGORIES_INDEX 1
-
-#define YOUTUBE_FEEDS_ID "standard-feeds"
-#define YOUTUBE_FEEDS_NAME "Standard feeds"
-
-#define YOUTUBE_CATEGORIES_ID "categories"
-#define YOUTUBE_CATEGORIES_NAME "Categories"
-#define YOUTUBE_CATEGORIES_URL "http://gdata.youtube.com/schemas/2007/categories.cat"
-
-/* ----- Feeds categories ---- */
-
-#define YOUTUBE_TOP_RATED_ID (YOUTUBE_FEEDS_ID "/0")
-#define YOUTUBE_TOP_RATED_NAME "Top Rated"
-
-#define YOUTUBE_TOP_FAVS_ID (YOUTUBE_FEEDS_ID "/1")
-#define YOUTUBE_TOP_FAVS_NAME "Top Favorites"
-
-#define YOUTUBE_MOST_VIEWED_ID (YOUTUBE_FEEDS_ID "/2")
-#define YOUTUBE_MOST_VIEWED_NAME "Most Viewed"
-
-#define YOUTUBE_MOST_POPULAR_ID (YOUTUBE_FEEDS_ID "/3")
-#define YOUTUBE_MOST_POPULAR_NAME "Most Popular"
-
-#define YOUTUBE_MOST_RECENT_ID (YOUTUBE_FEEDS_ID "/4")
-#define YOUTUBE_MOST_RECENT_NAME "Most Recent"
-
-#define YOUTUBE_MOST_DISCUSSED_ID (YOUTUBE_FEEDS_ID "/5")
-#define YOUTUBE_MOST_DISCUSSED_NAME "Most Discussed"
-
-#define YOUTUBE_MOST_LINKED_ID (YOUTUBE_FEEDS_ID "/6")
-#define YOUTUBE_MOST_LINKED_NAME "Most Linked"
-
-#define YOUTUBE_MOST_RESPONDED_ID (YOUTUBE_FEEDS_ID "/7")
-#define YOUTUBE_MOST_RESPONDED_NAME "Most Responded"
-
-#define YOUTUBE_FEATURED_ID (YOUTUBE_FEEDS_ID "/8")
-#define YOUTUBE_FEATURED_NAME "Recently Featured"
-
-#define YOUTUBE_MOBILE_ID (YOUTUBE_FEEDS_ID "/9")
-#define YOUTUBE_MOBILE_NAME "Watch On Mobile"
-
-/* --- Other --- */
-
-#define YOUTUBE_MAX_CHUNK 50
-
-#define YOUTUBE_VIDEO_INFO_URL "http://www.youtube.com/get_video_info?video_id=%s"
-#define YOUTUBE_VIDEO_URL "http://www.youtube.com/get_video?video_id=%s&t=%s&asv="
-#define YOUTUBE_CATEGORY_URL "http://gdata.youtube.com/feeds/api/videos/-/%s?&start-index=%s&max-results=%s"
-#define YOUTUBE_WATCH_URL "http://www.youtube.com/watch?v="
-
-#define YOUTUBE_VIDEO_MIME "application/x-shockwave-flash"
-#define YOUTUBE_SITE_URL "www.youtube.com"
-
-
-/* --- Plugin information --- */
-
-#define PLUGIN_ID YOUTUBE_PLUGIN_ID
-
-#define SOURCE_ID "grl-youtube"
-#define SOURCE_NAME "Youtube"
-#define SOURCE_DESC "A source for browsing and searching Youtube videos"
-
-#define AUTHOR "Igalia S.L."
-#define LICENSE "LGPL"
-#define SITE "http://www.igalia.com"
-
-/* --- Data types --- */
-
-typedef void (*AsyncReadCbFunc) (gchar *data, gpointer user_data);
-
-typedef void (*BuildMediaFromEntryCbFunc) (GrlMedia *media, gpointer user_data);
-
-typedef struct {
- gchar *id;
- gchar *name;
- guint count;
-} CategoryInfo;
-
-typedef struct {
- GrlMediaSource *source;
- guint operation_id;
- const gchar *container_id;
- GList *keys;
- GrlMetadataResolutionFlags flags;
- guint skip;
- guint count;
- GrlMediaSourceResultCb callback;
- gpointer user_data;
- guint error_code;
- CategoryInfo *category_info;
- guint emitted;
- guint matches;
- guint ref_count;
-} OperationSpec;
-
-typedef struct {
- GDataService *service;
- CategoryInfo *category_info;
-} CategoryCountCb;
-
-typedef struct {
- AsyncReadCbFunc callback;
- gchar *url;
- gpointer user_data;
-} AsyncReadCb;
-
-typedef struct {
- GrlMedia *media;
- BuildMediaFromEntryCbFunc callback;
- gpointer user_data;
-} SetMediaUrlAsyncReadCb;
-
-typedef enum {
- YOUTUBE_MEDIA_TYPE_ROOT,
- YOUTUBE_MEDIA_TYPE_FEEDS,
- YOUTUBE_MEDIA_TYPE_CATEGORIES,
- YOUTUBE_MEDIA_TYPE_FEED,
- YOUTUBE_MEDIA_TYPE_CATEGORY,
- YOUTUBE_MEDIA_TYPE_VIDEO,
-} YoutubeMediaType;
-
-struct _GrlYoutubeSourcePriv {
- GDataService *service;
-
- GrlNetWc *wc;
-};
-
-#define YOUTUBE_CLIENT_ID "grilo"
-
-static GrlYoutubeSource *grl_youtube_source_new (const gchar *api_key,
- const gchar *client_id);
-
-static void grl_youtube_source_set_property (GObject *object,
- guint propid,
- const GValue *value,
- GParamSpec *pspec);
-static void grl_youtube_source_finalize (GObject *object);
-
-gboolean grl_youtube_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs);
-
-static const GList *grl_youtube_source_supported_keys (GrlMetadataSource *source);
-
-static const GList *grl_youtube_source_slow_keys (GrlMetadataSource *source);
-
-static void grl_youtube_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss);
-
-static void grl_youtube_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs);
-
-static void grl_youtube_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms);
-
-static gboolean grl_youtube_test_media_from_uri (GrlMediaSource *source,
- const gchar *uri);
-
-static void grl_youtube_get_media_from_uri (GrlMediaSource *source,
- GrlMediaSourceMediaFromUriSpec *mfus);
-
-static void build_directories (GDataService *service);
-static void compute_feed_counts (GDataService *service);
-static void compute_category_counts (GDataService *service);
-
-/* ==================== Global Data ================= */
-
-guint root_dir_size = 2;
-CategoryInfo root_dir[] = {
- {YOUTUBE_FEEDS_ID, YOUTUBE_FEEDS_NAME, 10},
- {YOUTUBE_CATEGORIES_ID, YOUTUBE_CATEGORIES_NAME, 0},
- {NULL, NULL, 0}
-};
-
-CategoryInfo feeds_dir[] = {
- {YOUTUBE_TOP_RATED_ID, YOUTUBE_TOP_RATED_NAME, 0},
- {YOUTUBE_TOP_FAVS_ID, YOUTUBE_TOP_FAVS_NAME, 0},
- {YOUTUBE_MOST_VIEWED_ID, YOUTUBE_MOST_VIEWED_NAME, 0},
- {YOUTUBE_MOST_POPULAR_ID, YOUTUBE_MOST_POPULAR_NAME, 0},
- {YOUTUBE_MOST_RECENT_ID, YOUTUBE_MOST_RECENT_NAME, 0},
- {YOUTUBE_MOST_DISCUSSED_ID, YOUTUBE_MOST_DISCUSSED_NAME, 0},
- {YOUTUBE_MOST_LINKED_ID, YOUTUBE_MOST_LINKED_NAME, 0},
- {YOUTUBE_MOST_RESPONDED_ID, YOUTUBE_MOST_RESPONDED_NAME, 0},
- {YOUTUBE_FEATURED_ID, YOUTUBE_FEATURED_NAME, 0},
- {YOUTUBE_MOBILE_ID, YOUTUBE_MOBILE_NAME, 0},
- {NULL, NULL, 0}
-};
-
-CategoryInfo *categories_dir = NULL;
-
-static GrlYoutubeSource *ytsrc = NULL;
-
-/* =================== Youtube Plugin =============== */
-
-gboolean
-grl_youtube_plugin_init (GrlPluginRegistry *registry,
- const GrlPluginInfo *plugin,
- GList *configs)
-{
- gchar *api_key;
- GrlConfig *config;
- gint config_count;
-
- GRL_LOG_DOMAIN_INIT (youtube_log_domain, "youtube");
-
- GRL_DEBUG ("youtube_plugin_init");
-
- if (!configs) {
- GRL_WARNING ("Configuration not provided! Cannot configure plugin.");
- return FALSE;
- }
-
- config_count = g_list_length (configs);
- if (config_count > 1) {
- GRL_WARNING ("Provided %d configs, but will only use one", config_count);
- }
-
- config = GRL_CONFIG (configs->data);
- api_key = grl_config_get_api_key (config);
- if (!api_key) {
- GRL_WARNING ("Missing API Key, cannot configure Youtube plugin");
- return FALSE;
- }
-
- /* libgdata needs this */
- if (!g_thread_supported()) {
- g_thread_init (NULL);
- }
-
- GrlYoutubeSource *source =
- grl_youtube_source_new (api_key, YOUTUBE_CLIENT_ID);
-
- grl_plugin_registry_register_source (registry,
- plugin,
- GRL_MEDIA_PLUGIN (source),
- NULL);
-
- g_free (api_key);
-
- return TRUE;
-}
-
-GRL_PLUGIN_REGISTER (grl_youtube_plugin_init,
- NULL,
- PLUGIN_ID);
-
-/* ================== Youtube GObject ================ */
-
-G_DEFINE_TYPE (GrlYoutubeSource, grl_youtube_source, GRL_TYPE_MEDIA_SOURCE);
-
-static GrlYoutubeSource *
-grl_youtube_source_new (const gchar *api_key, const gchar *client_id)
-{
- GRL_DEBUG ("grl_youtube_source_new");
-
- GrlYoutubeSource *source;
- GDataYouTubeService *service;
-
- service = gdata_youtube_service_new (api_key, client_id);
- if (!service) {
- GRL_WARNING ("Failed to initialize gdata service");
- return NULL;
- }
-
- /* Use auto-split mode because Youtube fails for queries
- that request more than YOUTUBE_MAX_CHUNK results */
- source = GRL_YOUTUBE_SOURCE (g_object_new (GRL_YOUTUBE_SOURCE_TYPE,
- "source-id", SOURCE_ID,
- "source-name", SOURCE_NAME,
- "source-desc", SOURCE_DESC,
- "auto-split-threshold",
- YOUTUBE_MAX_CHUNK,
- "yt-service", service,
- NULL));
-
- ytsrc = source;
-
- /* Build browse content hierarchy:
- - Query Youtube for available categories
- - Compute category childcounts
- We only need to do this once */
- if (!categories_dir) {
- build_directories (GDATA_SERVICE (service));
- }
-
- return source;
-}
-
-static void
-grl_youtube_source_class_init (GrlYoutubeSourceClass * klass)
-{
- GrlMediaSourceClass *source_class = GRL_MEDIA_SOURCE_CLASS (klass);
- GrlMetadataSourceClass *metadata_class = GRL_METADATA_SOURCE_CLASS (klass);
- GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
- source_class->search = grl_youtube_source_search;
- source_class->browse = grl_youtube_source_browse;
- source_class->metadata = grl_youtube_source_metadata;
- source_class->test_media_from_uri = grl_youtube_test_media_from_uri;
- source_class->media_from_uri = grl_youtube_get_media_from_uri;
- metadata_class->supported_keys = grl_youtube_source_supported_keys;
- metadata_class->slow_keys = grl_youtube_source_slow_keys;
- gobject_class->set_property = grl_youtube_source_set_property;
- gobject_class->finalize = grl_youtube_source_finalize;
-
- g_object_class_install_property (gobject_class,
- PROP_SERVICE,
- g_param_spec_object ("yt-service",
- "youtube-service",
- "gdata youtube service object",
- GDATA_TYPE_YOUTUBE_SERVICE,
- G_PARAM_WRITABLE
- | G_PARAM_CONSTRUCT_ONLY
- | G_PARAM_STATIC_NAME));
-
- g_type_class_add_private (klass, sizeof (GrlYoutubeSourcePriv));
-}
-
-static void
-grl_youtube_source_init (GrlYoutubeSource *source)
-{
- source->priv = GRL_YOUTUBE_SOURCE_GET_PRIVATE (source);
-}
-
-static void
-grl_youtube_source_set_property (GObject *object,
- guint propid,
- const GValue *value,
- GParamSpec *pspec)
-
-{
- switch (propid) {
- case PROP_SERVICE: {
- GrlYoutubeSource *self;
- self = GRL_YOUTUBE_SOURCE (object);
- self->priv->service = g_value_get_object (value);
- break;
- }
- default:
- G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec);
- }
-}
-
-static void
-grl_youtube_source_finalize (GObject *object)
-{
- GrlYoutubeSource *self;
-
- self = GRL_YOUTUBE_SOURCE (object);
-
- if (self->priv->wc)
- g_object_unref (self->priv->wc);
-
- if (self->priv->service)
- g_object_unref (self->priv->service);
-
- G_OBJECT_CLASS (grl_youtube_source_parent_class)->finalize (object);
-}
-
-/* ======================= Utilities ==================== */
-
-static OperationSpec *
-operation_spec_new ()
-{
- GRL_DEBUG ("Allocating new spec");
- OperationSpec *os = g_slice_new0 (OperationSpec);
- os->ref_count = 1;
- return os;
-}
-
-static void
-operation_spec_unref (OperationSpec *os)
-{
- os->ref_count--;
- if (os->ref_count == 0) {
- g_slice_free (OperationSpec, os);
- GRL_DEBUG ("freeing spec");
- }
-}
-
-static void
-operation_spec_ref (OperationSpec *os)
-{
- GRL_DEBUG ("Reffing spec");
- os->ref_count++;
-}
-
-inline static GrlNetWc *
-get_wc ()
-{
- if (ytsrc && !ytsrc->priv->wc)
- ytsrc->priv->wc = grl_net_wc_new ();
-
- return ytsrc->priv->wc;
-}
-
-static void
-read_done_cb (GObject *source_object,
- GAsyncResult *res,
- gpointer user_data)
-{
- AsyncReadCb *arc = (AsyncReadCb *) user_data;
- GError *wc_error = NULL;
- gchar *content = NULL;
-
- grl_net_wc_request_finish (GRL_NET_WC (source_object),
- res,
- &content,
- NULL,
- &wc_error);
- if (wc_error) {
- GRL_WARNING ("Failed to open '%s': %s", arc->url, wc_error->message);
- arc->callback (NULL, arc->user_data);
- g_error_free (wc_error);
- } else {
- arc->callback (content, arc->user_data);
- }
- g_free (arc->url);
- g_slice_free (AsyncReadCb, arc);
-}
-
-static void
-read_url_async (const gchar *url,
- AsyncReadCbFunc callback,
- gpointer user_data)
-{
- AsyncReadCb *arc;
-
- arc = g_slice_new0 (AsyncReadCb);
- arc->url = g_strdup (url);
- arc->callback = callback;
- arc->user_data = user_data;
-
- GRL_DEBUG ("Opening async '%s'", url);
- grl_net_wc_request_async (get_wc (),
- url,
- NULL,
- read_done_cb,
- arc);
-}
-
-static void
-set_media_url_async_read_cb (gchar *data, gpointer user_data)
-{
- SetMediaUrlAsyncReadCb *cb_data = (SetMediaUrlAsyncReadCb *) user_data;
- gchar *url = NULL;
- GMatchInfo *match_info = NULL;
- static GRegex *regex = NULL;
-
- if (!data) {
- goto done;
- }
-
- if (regex == NULL) {
- regex = g_regex_new (".*&fmt_url_map=([^&]+)&", G_REGEX_OPTIMIZE, 0, NULL);
- }
-
- /* Check if we find the url mapping */
- g_regex_match (regex, data, 0, &match_info);
- if (g_match_info_matches (match_info) == TRUE) {
- gchar *url_map_escaped, *url_map;
- gchar **mappings;
-
- url_map_escaped = g_match_info_fetch (match_info, 1);
- url_map = g_uri_unescape_string (url_map_escaped, NULL);
- g_free (url_map_escaped);
-
- mappings = g_strsplit (url_map, ",", 0);
- g_free (url_map);
-
- if (mappings != NULL) {
- /* TODO: We get the URL from the first format available.
- * We should provide the list of available urls or let the user
- * configure preferred formats
- */
- gchar **mapping = g_strsplit (mappings[0], "|", 2);
- url = g_strdup (mapping[1]);
- g_strfreev (mapping);
- }
- } else {
- GRL_DEBUG ("Format array not found, using token workaround");
- gchar *token_start;
- gchar *token_end;
- gchar *token;
- const gchar *video_id;
-
- token_start = g_strrstr (data, "&token=");
- if (!token_start) {
- goto done;
- }
- token_start += 7;
- token_end = strstr (token_start, "&");
- token = g_strndup (token_start, token_end - token_start);
-
- video_id = grl_media_get_id (cb_data->media);
- url = g_strdup_printf (YOUTUBE_VIDEO_URL, video_id, token);
- g_free (token);
- }
-
- done:
- if (url) {
- grl_media_set_url (cb_data->media, url);
- g_free (url);
- }
-
- cb_data->callback (cb_data->media, cb_data->user_data);
-
- g_free (cb_data);
-}
-
-static void
-set_media_url (GrlMedia *media,
- BuildMediaFromEntryCbFunc callback,
- gpointer user_data)
-{
- const gchar *video_id;
- gchar *video_info_url;
- SetMediaUrlAsyncReadCb *set_media_url_async_read_data;
-
- /* The procedure to get the video url is:
- * 1) Read the video info URL using the video id (async operation)
- * 2) In the video info page, there should be an array of supported formats
- * and their corresponding URLs, right now we just use the first one we get.
- * (see set_media_url_async_read_cb).
- * TODO: we should be able to provide various urls or at least
- * select preferred formats via configuration
- * TODO: we should set mime-type accordingly to the format selected
- * 3) As a workaround in case no format array is found we get the video token
- * and figure out the url of the video using the video id and the token.
- */
-
- video_id = grl_media_get_id (media);
- video_info_url = g_strdup_printf (YOUTUBE_VIDEO_INFO_URL, video_id);
-
- set_media_url_async_read_data = g_new0 (SetMediaUrlAsyncReadCb, 1);
- set_media_url_async_read_data->media = media;
- set_media_url_async_read_data->callback = callback;
- set_media_url_async_read_data->user_data = user_data;
-
- read_url_async (video_info_url,
- set_media_url_async_read_cb,
- set_media_url_async_read_data);
-
- g_free (video_info_url);
-}
-
-static void
-build_media_from_entry (GrlMedia *content,
- GDataEntry *entry,
- const GList *keys,
- BuildMediaFromEntryCbFunc callback,
- gpointer user_data)
-{
- GDataYouTubeVideo *video;
- GrlMedia *media;
- GList *iter;
- gboolean need_url = FALSE;
-
- if (!content) {
- media = grl_media_video_new ();
- } else {
- media = content;
- }
-
- video = GDATA_YOUTUBE_VIDEO (entry);
-
- /* Make sure we set the media id in any case */
- if (!grl_media_get_id (media)) {
- grl_media_set_id (media, gdata_youtube_video_get_video_id (video));
- }
-
- iter = (GList *) keys;
- while (iter) {
- if (iter->data == GRL_METADATA_KEY_TITLE) {
- grl_media_set_title (media, gdata_entry_get_title (entry));
- } else if (iter->data == GRL_METADATA_KEY_DESCRIPTION) {
- grl_media_set_description (media,
- gdata_youtube_video_get_description (video));
- } else if (iter->data == GRL_METADATA_KEY_THUMBNAIL) {
- GList *thumb_list;
- thumb_list = gdata_youtube_video_get_thumbnails (video);
- if (thumb_list) {
- GDataMediaThumbnail *thumbnail;
- thumbnail = GDATA_MEDIA_THUMBNAIL (thumb_list->data);
- grl_media_set_thumbnail (media,
- gdata_media_thumbnail_get_uri (thumbnail));
- }
- } else if (iter->data == GRL_METADATA_KEY_DATE) {
- GTimeVal date;
- gchar *date_str;
-#ifdef GDATA_API_SUBJECT_TO_CHANGE
- gint64 published = gdata_entry_get_published (entry);
- date.tv_sec = (glong) published;
-#else
- gdata_entry_get_published (entry, &date);
-#endif
- if (date.tv_sec != 0 || date.tv_usec != 0) {
- date_str = g_time_val_to_iso8601 (&date);
- grl_media_set_date (media, date_str);
- g_free (date_str);
- }
- } else if (iter->data == GRL_METADATA_KEY_DURATION) {
- grl_media_set_duration (media, gdata_youtube_video_get_duration (video));
- } else if (iter->data == GRL_METADATA_KEY_MIME) {
- grl_media_set_mime (media, YOUTUBE_VIDEO_MIME);
- } else if (iter->data == GRL_METADATA_KEY_SITE) {
- grl_media_set_site (media, gdata_youtube_video_get_player_uri (video));
- } else if (iter->data == GRL_METADATA_KEY_EXTERNAL_URL) {
- grl_media_set_external_url (media,
- gdata_youtube_video_get_player_uri (video));
- } else if (iter->data == GRL_METADATA_KEY_RATING) {
- gdouble average;
- gdata_youtube_video_get_rating (video, NULL, NULL, NULL, &average);
- grl_media_set_rating (media, average, 5.00);
- } else if (iter->data == GRL_METADATA_KEY_URL) {
- /* This needs another query and will be resolved asynchronously p Q*/
- need_url = TRUE;
- } else if (iter->data == GRL_METADATA_KEY_EXTERNAL_PLAYER) {
- GDataYouTubeContent *youtube_content;
- youtube_content =
- gdata_youtube_video_look_up_content (video,
- "application/x-shockwave-flash");
- if (youtube_content != NULL) {
- GDataMediaContent *content = GDATA_MEDIA_CONTENT (youtube_content);
- grl_media_set_external_player (media,
- gdata_media_content_get_uri (content));
- }
- }
- iter = g_list_next (iter);
- }
-
- if (need_url) {
- /* URL resolution is async */
- set_media_url (media, callback, user_data);
- } else {
- callback (media, user_data);
- }
-}
-
-static void
-parse_categories (xmlDocPtr doc, xmlNodePtr node, GDataService *service)
-{
- GRL_DEBUG ("parse_categories");
-
- guint total = 0;
- GList *all = NULL, *iter;
- CategoryInfo *cat_info;
- gchar *id;
- guint index = 0;
-
- while (node) {
- cat_info = g_slice_new (CategoryInfo);
- id = (gchar *) xmlGetProp (node, (xmlChar *) "term");
- cat_info->id = g_strconcat (YOUTUBE_CATEGORIES_ID, "/", id, NULL);
- cat_info->name = (gchar *) xmlGetProp (node, (xmlChar *) "label");
- all = g_list_prepend (all, cat_info);
- g_free (id);
- node = node->next;
- total++;
- GRL_DEBUG (" Found category: '%d - %s'", index++, cat_info->name);
- }
-
- if (all) {
- root_dir[ROOT_DIR_CATEGORIES_INDEX].count = total;
- categories_dir = g_new0 (CategoryInfo, total + 1);
- iter = all;
- do {
- cat_info = (CategoryInfo *) iter->data;
- categories_dir[total - 1].id = cat_info->id ;
- categories_dir[total - 1].name = cat_info->name;
- categories_dir[total - 1].count = 0;
- total--;
- g_slice_free (CategoryInfo, cat_info);
- iter = g_list_next (iter);
- } while (iter);
- g_list_free (all);
-
- compute_category_counts (service);
- }
-}
-
-static void
-build_categories_directory_read_cb (gchar *xmldata, gpointer user_data)
-{
- xmlDocPtr doc;
- xmlNodePtr node;
-
- if (!xmldata) {
- g_critical ("Failed to build category directory (1)");
- return;
- }
-
- doc = xmlReadMemory (xmldata, strlen (xmldata), NULL, NULL,
- XML_PARSE_RECOVER | XML_PARSE_NOBLANKS);
- if (!doc) {
- g_critical ("Failed to build category directory (2)");
- goto free_resources;
- }
-
- node = xmlDocGetRootElement (doc);
- if (!node) {
- g_critical ("Failed to build category directory (3)");
- goto free_resources;
- }
-
- if (xmlStrcmp (node->name, (const xmlChar *) "categories")) {
- g_critical ("Failed to build category directory (4)");
- goto free_resources;
- }
-
- node = node->xmlChildrenNode;
- if (!node) {
- g_critical ("Failed to build category directory (5)");
- goto free_resources;
- }
-
- parse_categories (doc, node, GDATA_SERVICE (user_data));
-
- free_resources:
- xmlFreeDoc (doc);
-}
-
-static gint
-get_feed_type_from_id (const gchar *feed_id)
-{
- gchar *tmp;
- gchar *test;
- gint feed_type;
-
- tmp = g_strrstr (feed_id, "/");
- if (!tmp) {
- return -1;
- }
- tmp++;
-
- feed_type = strtol (tmp, &test, 10);
- if (*test != '\0') {
- return -1;
- }
-
- return feed_type;
-}
-
-static const gchar *
-get_category_term_from_id (const gchar *category_id)
-{
- gchar *term;
- term = g_strrstr (category_id, "/");
- if (!term) {
- return NULL;
- }
- return ++term;
-}
-
-static gint
-get_category_index_from_id (const gchar *category_id)
-{
- gint i;
-
- for (i=0; i<root_dir[ROOT_DIR_CATEGORIES_INDEX].count; i++) {
- if (!strcmp (categories_dir[i].id, category_id)) {
- return i;
- }
- }
- return -1;
-}
-
-static void
-item_count_cb (GObject *object, GAsyncResult *result, CategoryCountCb *cc)
-{
- GRL_DEBUG ("item_count_cb");
-
- GDataFeed *feed;
- GError *error = NULL;
-
- feed = gdata_service_query_finish (GDATA_SERVICE (cc->service),
- result, &error);
- if (error) {
- GRL_WARNING ("Failed to compute count for category '%s': %s",
- cc->category_info->id, error->message);
- g_error_free (error);
- } else if (feed) {
- cc->category_info->count = gdata_feed_get_total_results (feed);
- GRL_DEBUG ("Category '%s' - childcount: '%u'",
- cc->category_info->id, cc->category_info->count);
- }
-
- if (feed) {
- g_object_unref (feed);
- }
- g_slice_free (CategoryCountCb, cc);
-}
-
-static void
-compute_category_counts (GDataService *service)
-{
- gint i;
-
- GRL_DEBUG ("compute_category_counts");
-
- for (i=0; i<root_dir[ROOT_DIR_CATEGORIES_INDEX].count; i++) {
- GRL_DEBUG ("Computing chilcount for category '%s'", categories_dir[i].id);
- GDataQuery *query = gdata_query_new_with_limits (NULL, 0, 1);
- const gchar *category_term =
- get_category_term_from_id (categories_dir[i].id);
- gdata_query_set_categories (query, category_term);
- CategoryCountCb *cc = g_slice_new (CategoryCountCb);
- cc->service = service;
- cc->category_info = &categories_dir[i];
- gdata_youtube_service_query_videos_async (GDATA_YOUTUBE_SERVICE (service),
- query,
- NULL, NULL, NULL,
- (GAsyncReadyCallback) item_count_cb,
- cc);
- g_object_unref (query);
- }
-}
-
-static void
-compute_feed_counts (GDataService *service)
-{
- gint i;
- GRL_DEBUG ("compute_feed_counts");
-
- for (i=0; i<root_dir[ROOT_DIR_FEEDS_INDEX].count; i++) {
- GRL_DEBUG ("Computing chilcount for feed '%s'", feeds_dir[i].id);
- gint feed_type = get_feed_type_from_id (feeds_dir[i].id);
- GDataQuery *query = gdata_query_new_with_limits (NULL, 0, 1);
- CategoryCountCb *cc = g_slice_new (CategoryCountCb);
- cc->service = service;
- cc->category_info = &feeds_dir[i];
- gdata_youtube_service_query_standard_feed_async (GDATA_YOUTUBE_SERVICE (service),
- feed_type,
- query,
- NULL, NULL, NULL,
- (GAsyncReadyCallback) item_count_cb,
- cc);
- g_object_unref (query);
- }
-}
-
-static void
-build_media_from_entry_metadata_cb (GrlMedia *media, gpointer user_data)
-{
- GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
- ms->callback (ms->source, media, ms->user_data, NULL);
-}
-
-static void
-build_media_from_entry_search_cb (GrlMedia *media, gpointer user_data)
-{
- /*
- * TODO: Async resolution of URL messes (or could mess) with the sorting,
- * If we want to ensure a particular sorting or implement sorting
- * mechanisms we should add code to handle that here so we emit items in
- * the right order and not just when we got the URL resolved (would
- * damage response time though).
- */
- OperationSpec *os = (OperationSpec *) user_data;
- guint remaining;
-
- if (os->emitted < os->count) {
- remaining = os->count - os->emitted - 1;
- os->callback (os->source,
- os->operation_id,
- media,
- remaining,
- os->user_data,
- NULL);
- if (remaining == 0) {
- GRL_DEBUG ("Unreffing spec in build_media_from_entry_search_cb");
- operation_spec_unref (os);
- } else {
- os->emitted++;
- }
- }
-}
-
-static void
-build_directories (GDataService *service)
-{
- GRL_DEBUG ("build_drectories");
-
- /* Parse category list from Youtube and compute category counts */
- read_url_async (YOUTUBE_CATEGORIES_URL,
- build_categories_directory_read_cb,
- service);
-
- /* Compute feed counts */
- compute_feed_counts (service);
-}
-
-static void
-metadata_cb (GObject *object,
- GAsyncResult *result,
- gpointer user_data)
-{
- GRL_DEBUG ("metadata_cb");
-
- GError *error = NULL;
- GrlYoutubeSource *source;
- GDataEntry *video;
- GDataService *service;
- GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
-
- source = GRL_YOUTUBE_SOURCE (ms->source);
- service = GDATA_SERVICE (source->priv->service);
-
-#ifdef GDATA_API_SUBJECT_TO_CHANGE
- video = gdata_service_query_single_entry_finish (service, result, &error);
-#else
- video =
- GDATA_ENTRY (gdata_youtube_service_query_single_video_finish
- (GDATA_YOUTUBE_SERVICE (service), result, &error));
-#endif
- if (error) {
- error->code = GRL_CORE_ERROR_METADATA_FAILED;
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- } else {
- build_media_from_entry (ms->media, video, ms->keys,
- build_media_from_entry_metadata_cb, ms);
- }
-
- if (video) {
- g_object_unref (video);
- }
-}
-
-static void
-search_progress_cb (GDataEntry *entry,
- guint index,
- guint count,
- gpointer user_data)
-{
- OperationSpec *os = (OperationSpec *) user_data;
- if (index < count) {
- /* Keep track of the items we got here. Due to the asynchronous
- * nature of build_media_from_entry(), when search_cb is invoked
- * we have to check if we got as many results as we requested or
- * not, and handle that situation properly */
- os->matches++;
- build_media_from_entry (NULL, entry, os->keys,
- build_media_from_entry_search_cb, os);
- } else {
- GRL_WARNING ("Invalid index/count received grom libgdata, ignoring result");
- }
-
- /* The entry will be freed when freeing the feed in search_cb */
-}
-
-static void
-search_cb (GObject *object, GAsyncResult *result, OperationSpec *os)
-{
- GRL_DEBUG ("search_cb");
-
- GDataFeed *feed;
- GError *error = NULL;
- gboolean need_extra_unref = FALSE;
- GrlYoutubeSource *source = GRL_YOUTUBE_SOURCE (os->source);
-
- feed = gdata_service_query_finish (source->priv->service, result, &error);
- if (!error && feed) {
- /* If we are browsing a category, update the count for it */
- if (os->category_info) {
- os->category_info->count = gdata_feed_get_total_results (feed);
- }
-
- /* Check if we got as many results as we requested */
- if (os->matches < os->count) {
- os->count = os->matches;
- /* In case we are resolving URLs asynchronously, from now on
- * results will be sent with appropriate remaining, but it can
- * also be the case that we have sent all the results already
- * and the last one was sent with remaining>0, in that case
- * we should send a finishing message now. */
- if (os->emitted == os->count) {
- GRL_DEBUG ("sending finishing message");
- os->callback (os->source, os->operation_id,
- NULL, 0, os->user_data, NULL);
- need_extra_unref = TRUE;
- }
- }
- } else {
- if (!error) {
- error = g_error_new (GRL_CORE_ERROR,
- os->error_code,
- "Failed to obtain feed from Youtube");
- } else {
- error->code = os->error_code;
- }
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
- g_error_free (error);
- need_extra_unref = TRUE;
- }
-
- if (feed)
- g_object_unref (feed);
-
- GRL_DEBUG ("Unreffing spec in search_cb");
- operation_spec_unref (os);
- if (need_extra_unref) {
- /* We did not free the spec in the emission callback, do it here */
- GRL_DEBUG ("need extra spec unref in search_cb");
- operation_spec_unref (os);
- }
-}
-
-static gboolean
-is_category_container (const gchar *container_id)
-{
- return g_str_has_prefix (container_id, YOUTUBE_CATEGORIES_ID "/");
-}
-
-static gboolean
-is_feeds_container (const gchar *container_id)
-{
- return g_str_has_prefix (container_id, YOUTUBE_FEEDS_ID "/");
-}
-
-static YoutubeMediaType
-classify_media_id (const gchar *media_id)
-{
- if (!media_id) {
- return YOUTUBE_MEDIA_TYPE_ROOT;
- } else if (!strcmp (media_id, YOUTUBE_FEEDS_ID)) {
- return YOUTUBE_MEDIA_TYPE_FEEDS;
- } else if (!strcmp (media_id, YOUTUBE_CATEGORIES_ID)) {
- return YOUTUBE_MEDIA_TYPE_CATEGORIES;
- } else if (is_category_container (media_id)) {
- return YOUTUBE_MEDIA_TYPE_CATEGORY;
- } else if (is_feeds_container (media_id)) {
- return YOUTUBE_MEDIA_TYPE_FEED;
- } else {
- return YOUTUBE_MEDIA_TYPE_VIDEO;
- }
-}
-
-static void
-set_category_childcount (GDataService *service,
- GrlMediaBox *content,
- CategoryInfo *dir,
- guint index)
-{
- gint childcount;
- gboolean set_childcount = TRUE;
- const gchar *container_id;
-
- container_id = grl_media_get_id (GRL_MEDIA (content));
-
- if (dir == NULL) {
- /* Special case: we want childcount of root category */
- childcount = root_dir_size;
- } else if (!strcmp (dir[index].id, YOUTUBE_FEEDS_ID)) {
- childcount = root_dir[ROOT_DIR_FEEDS_INDEX].count;
- } else if (!strcmp (dir[index].id, YOUTUBE_CATEGORIES_ID)) {
- childcount = root_dir[ROOT_DIR_CATEGORIES_INDEX].count;
- } else if (is_feeds_container (container_id)) {
- gint feed_index = get_feed_type_from_id (container_id);
- if (feed_index >= 0) {
- childcount = feeds_dir[feed_index].count;
- } else {
- set_childcount = FALSE;
- }
- } else if (is_category_container (container_id)) {
- gint cat_index = get_category_index_from_id (container_id);
- if (cat_index >= 0) {
- childcount = categories_dir[cat_index].count;
- } else {
- set_childcount = FALSE;
- }
- } else {
- set_childcount = FALSE;
- }
-
- if (set_childcount) {
- grl_media_box_set_childcount (content, childcount);
- }
-}
-
-static GrlMedia *
-produce_container_from_directory (GDataService *service,
- GrlMedia *media,
- CategoryInfo *dir,
- guint index)
-{
- GrlMedia *content;
-
- if (!media) {
- /* Create mode */
- content = grl_media_box_new ();
- } else {
- /* Update mode */
- content = media;
- }
-
- if (!dir) {
- grl_media_set_id (content, NULL);
- grl_media_set_title (content, YOUTUBE_ROOT_NAME);
- } else {
- grl_media_set_id (content, dir[index].id);
- grl_media_set_title (content, dir[index].name);
- }
- grl_media_set_site (content, YOUTUBE_SITE_URL);
- set_category_childcount (service, GRL_MEDIA_BOX (content), dir, index);
-
- return content;
-}
-
-static void
-produce_from_directory (CategoryInfo *dir, guint dir_size, OperationSpec *os)
-{
- GRL_DEBUG ("produce_from_directory");
-
- guint index, remaining;
-
- /* Youtube's first index is 1, but the directories start at 0 */
- os->skip--;
-
- if (os->skip >= dir_size) {
- /* No results */
- os->callback (os->source,
- os->operation_id,
- NULL,
- 0,
- os->user_data,
- NULL);
- operation_spec_unref (os);
- } else {
- index = os->skip;
- remaining = MIN (dir_size - os->skip, os->count);
-
- do {
- GDataService *service = GRL_YOUTUBE_SOURCE (os->source)->priv->service;
-
- GrlMedia *content =
- produce_container_from_directory (service, NULL, dir, index);
-
- remaining--;
- index++;
-
- os->callback (os->source,
- os->operation_id,
- content,
- remaining,
- os->user_data,
- NULL);
-
- if (remaining == 0) {
- operation_spec_unref (os);
- }
- } while (remaining > 0);
- }
-}
-
-static void
-produce_from_feed (OperationSpec *os)
-{
- GError *error = NULL;
- gint feed_type;
- GDataQuery *query;
- GDataService *service;
-
- feed_type = get_feed_type_from_id (os->container_id);
-
- if (feed_type < 0) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Invalid feed id: %s", os->container_id);
- os->callback (os->source,
- os->operation_id,
- NULL,
- 0,
- os->user_data,
- error);
- g_error_free (error);
- operation_spec_unref (os);
- return;
- }
- /* OPERATION_SPEC_REF_RATIONALE
- * Depending on wether the URL has been requested, metadata resolution
- * for each item in the result set may or may not be asynchronous.
- * We cannot free the spec in search_cb because that may be called
- * before the asynchronous URL resolution is finished, and we cannot
- * do it in build_media_from_entry_search_cb either, because in the
- * synchronous case (when we do not request URL) search_cb will
- * be invoked after it.
- * Thus, the solution is to increase the reference count here and
- * have both places unreffing the spec, that way, no matter which
- * is invoked last, the spec will be freed only once. */
- operation_spec_ref (os);
-
- service = GRL_YOUTUBE_SOURCE (os->source)->priv->service;
- query = gdata_query_new_with_limits (NULL , os->skip, os->count);
- os->category_info = &feeds_dir[feed_type];
- gdata_youtube_service_query_standard_feed_async (GDATA_YOUTUBE_SERVICE (service),
- feed_type,
- query,
- NULL,
- search_progress_cb,
- os,
- (GAsyncReadyCallback) search_cb,
- os);
- g_object_unref (query);
-}
-
-static void
-produce_from_category (OperationSpec *os)
-{
- GError *error = NULL;
- GDataQuery *query;
- GDataService *service;
- const gchar *category_term;
- gint category_index;
-
- category_term = get_category_term_from_id (os->container_id);
- category_index = get_category_index_from_id (os->container_id);
-
- if (!category_term) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_BROWSE_FAILED,
- "Invalid category id: %s", os->container_id);
- os->callback (os->source,
- os->operation_id,
- NULL,
- 0,
- os->user_data,
- error);
- g_error_free (error);
- operation_spec_unref (os);
- return;
- }
-
- /* Look for OPERATION_SPEC_REF_RATIONALE for details */
- operation_spec_ref (os);
-
- service = GRL_YOUTUBE_SOURCE (os->source)->priv->service;
- query = gdata_query_new_with_limits (NULL , os->skip, os->count);
- os->category_info = &categories_dir[category_index];
- gdata_query_set_categories (query, category_term);
- gdata_youtube_service_query_videos_async (GDATA_YOUTUBE_SERVICE (service),
- query,
- NULL,
- search_progress_cb,
- os,
- (GAsyncReadyCallback) search_cb,
- os);
- g_object_unref (query);
-}
-
-static gchar *
-get_video_id_from_url (const gchar *url)
-{
- gchar *marker, *end, *video_id;
-
- if (url == NULL) {
- return NULL;
- }
-
- marker = strstr (url, YOUTUBE_WATCH_URL);
- if (!marker) {
- return NULL;
- }
-
- marker += strlen (YOUTUBE_WATCH_URL);
-
- end = marker;
- while (*end != '\0' && *end != '&') {
- end++;
- }
-
- video_id = g_strndup (marker, end - marker);
-
- return video_id;
-}
-
-static void
-build_media_from_entry_media_from_uri_cb (GrlMedia *media, gpointer user_data)
-{
- GrlMediaSourceMediaFromUriSpec *mfus =
- (GrlMediaSourceMediaFromUriSpec *) user_data;
- mfus->callback (mfus->source, media, mfus->user_data, NULL);
-}
-
-static void
-media_from_uri_cb (GObject *object, GAsyncResult *result, gpointer user_data)
-{
- GError *error = NULL;
- GrlYoutubeSource *source;
- GDataEntry *video;
- GDataService *service;
- GrlMediaSourceMediaFromUriSpec *mfus =
- (GrlMediaSourceMediaFromUriSpec *) user_data;
-
- source = GRL_YOUTUBE_SOURCE (mfus->source);
- service = GDATA_SERVICE (source->priv->service);
-
-#ifdef GDATA_API_SUBJECT_TO_CHANGE
- video = gdata_service_query_single_entry_finish (service, result, &error);
-#else
- video =
- GDATA_ENTRY (gdata_youtube_service_query_single_video_finish
- (GDATA_YOUTUBE_SERVICE (service), result, &error));
-#endif
-
- if (error) {
- error->code = GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED;
- mfus->callback (mfus->source, NULL, mfus->user_data, error);
- g_error_free (error);
- } else {
- build_media_from_entry (NULL, video, mfus->keys,
- build_media_from_entry_media_from_uri_cb,
- mfus);
- }
-
- if (video) {
- g_object_unref (video);
- }
-}
-
-/* ================== API Implementation ================ */
-
-static const GList *
-grl_youtube_source_supported_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_ID,
- GRL_METADATA_KEY_TITLE,
- GRL_METADATA_KEY_URL,
- GRL_METADATA_KEY_EXTERNAL_URL,
- GRL_METADATA_KEY_DESCRIPTION,
- GRL_METADATA_KEY_DURATION,
- GRL_METADATA_KEY_DATE,
- GRL_METADATA_KEY_THUMBNAIL,
- GRL_METADATA_KEY_MIME,
- GRL_METADATA_KEY_CHILDCOUNT,
- GRL_METADATA_KEY_SITE,
- GRL_METADATA_KEY_RATING,
- GRL_METADATA_KEY_EXTERNAL_PLAYER,
- NULL);
- }
- return keys;
-}
-
-static const GList *
-grl_youtube_source_slow_keys (GrlMetadataSource *source)
-{
- static GList *keys = NULL;
- if (!keys) {
- keys = grl_metadata_key_list_new (GRL_METADATA_KEY_URL,
- NULL);
- }
- return keys;
-}
-
-static void
-grl_youtube_source_search (GrlMediaSource *source,
- GrlMediaSourceSearchSpec *ss)
-{
- OperationSpec *os;
- GDataQuery *query;
-
- GRL_DEBUG ("grl_youtube_source_search (%u, %u)", ss->skip, ss->count);
-
- os = operation_spec_new ();
- os->source = source;
- os->operation_id = ss->search_id;
- os->keys = ss->keys;
- os->skip = ss->skip + 1;
- os->count = ss->count;
- os->callback = ss->callback;
- os->user_data = ss->user_data;
- os->error_code = GRL_CORE_ERROR_SEARCH_FAILED;
-
- /* Look for OPERATION_SPEC_REF_RATIONALE for details */
- operation_spec_ref (os);
-
- query = gdata_query_new_with_limits (ss->text, os->skip, os->count);
- gdata_youtube_service_query_videos_async (GDATA_YOUTUBE_SERVICE (GRL_YOUTUBE_SOURCE (source)->priv->service),
- query,
- NULL,
- search_progress_cb,
- os,
- (GAsyncReadyCallback) search_cb,
- os);
- g_object_unref (query);
-}
-
-static void
-grl_youtube_source_browse (GrlMediaSource *source,
- GrlMediaSourceBrowseSpec *bs)
-{
- OperationSpec *os;
- const gchar *container_id;
-
- GRL_DEBUG ("grl_youtube_source_browse: %s", grl_media_get_id (bs->container));
-
- container_id = grl_media_get_id (bs->container);
-
- os = operation_spec_new ();
- os->source = bs->source;
- os->operation_id = bs->browse_id;
- os->container_id = container_id;
- os->keys = bs->keys;
- os->flags = bs->flags;
- os->skip = bs->skip + 1;
- os->count = bs->count;
- os->callback = bs->callback;
- os->user_data = bs->user_data;
- os->error_code = GRL_CORE_ERROR_BROWSE_FAILED;
-
- switch (classify_media_id (container_id))
- {
- case YOUTUBE_MEDIA_TYPE_ROOT:
- produce_from_directory (root_dir, root_dir_size, os);
- break;
- case YOUTUBE_MEDIA_TYPE_FEEDS:
- produce_from_directory (feeds_dir,
- root_dir[ROOT_DIR_FEEDS_INDEX].count, os);
- break;
- case YOUTUBE_MEDIA_TYPE_CATEGORIES:
- produce_from_directory (categories_dir,
- root_dir[ROOT_DIR_CATEGORIES_INDEX].count, os);
- break;
- case YOUTUBE_MEDIA_TYPE_FEED:
- produce_from_feed (os);
- break;
- case YOUTUBE_MEDIA_TYPE_CATEGORY:
- produce_from_category (os);
- break;
- default:
- g_assert_not_reached ();
- break;
- }
-}
-
-static void
-grl_youtube_source_metadata (GrlMediaSource *source,
- GrlMediaSourceMetadataSpec *ms)
-{
- YoutubeMediaType media_type;
- const gchar *id;
- GDataService *service;
- GError *error = NULL;
- GrlMedia *media = NULL;
-
- GRL_DEBUG ("grl_youtube_source_metadata");
-
- id = grl_media_get_id (ms->media);
- media_type = classify_media_id (id);
- service = GRL_YOUTUBE_SOURCE (source)->priv->service;
-
- switch (media_type) {
- case YOUTUBE_MEDIA_TYPE_ROOT:
- media = produce_container_from_directory (service, ms->media, NULL, 0);
- break;
- case YOUTUBE_MEDIA_TYPE_FEEDS:
- media = produce_container_from_directory (service, ms->media, root_dir, 0);
- break;
- case YOUTUBE_MEDIA_TYPE_CATEGORIES:
- media = produce_container_from_directory (service, ms->media, root_dir, 1);
- break;
- case YOUTUBE_MEDIA_TYPE_FEED:
- {
- gint index = get_feed_type_from_id (id);
- if (index >= 0) {
- media = produce_container_from_directory (service, ms->media, feeds_dir,
- index);
- } else {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Invalid feed id");
- }
- }
- break;
- case YOUTUBE_MEDIA_TYPE_CATEGORY:
- {
- gint index = get_category_index_from_id (id);
- if (index >= 0) {
- media = produce_container_from_directory (service, ms->media,
- categories_dir, index);
- } else {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_METADATA_FAILED,
- "Invalid category id");
- }
- }
- break;
- case YOUTUBE_MEDIA_TYPE_VIDEO:
- default:
-#ifdef GDATA_API_SUBJECT_TO_CHANGE
- {
- gchar *entryid = g_strconcat ("tag:youtube.com,2008:video:", id, NULL);
- gdata_service_query_single_entry_async (service,
- entryid,
- NULL,
- GDATA_TYPE_YOUTUBE_VIDEO,
- NULL,
- metadata_cb,
- ms);
- g_free (entryid);
- }
-#else
- gdata_youtube_service_query_single_video_async (GDATA_YOUTUBE_SERVICE (service),
- NULL,
- id,
- NULL,
- metadata_cb,
- ms);
-#endif
- break;
- }
-
- if (error) {
- ms->callback (ms->source, ms->media, ms->user_data, error);
- g_error_free (error);
- } else if (media) {
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
- }
-}
-
-static gboolean
-grl_youtube_test_media_from_uri (GrlMediaSource *source, const gchar *uri)
-{
- GRL_DEBUG ("grl_youtube_test_media_from_uri");
-
- gchar *video_id;
- gboolean ok;
-
- video_id = get_video_id_from_url (uri);
- ok = (video_id != NULL);
- g_free (video_id);
- return ok;
-}
-
-static void
-grl_youtube_get_media_from_uri (GrlMediaSource *source,
- GrlMediaSourceMediaFromUriSpec *mfus)
-{
- GRL_DEBUG ("grl_youtube_get_media_from_uri");
-
- gchar *video_id;
- GError *error;
- GDataService *service;
-
- video_id = get_video_id_from_url (mfus->uri);
- if (video_id == NULL) {
- error = g_error_new (GRL_CORE_ERROR,
- GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED,
- "Cannot create media from '%s'", mfus->uri);
- mfus->callback (source, NULL, mfus->user_data, error);
- g_error_free (error);
- return;
- }
-
- service = GRL_YOUTUBE_SOURCE (source)->priv->service;
-
-#ifdef GDATA_API_SUBJECT_TO_CHANGE
- gchar *entry_id = g_strconcat ("tag:youtube.com,2008:video:", video_id, NULL);
- gdata_service_query_single_entry_async (service,
- entry_id,
- NULL,
- GDATA_TYPE_YOUTUBE_VIDEO,
- NULL,
- media_from_uri_cb,
- mfus);
- g_free (entry_id);
-#else
- gdata_youtube_service_query_single_video_async (GDATA_YOUTUBE_SERVICE (service),
- NULL,
- video_id,
- NULL,
- media_from_uri_cb,
- mfus);
-#endif
-}
diff --git a/src/youtube/grl-youtube.h b/src/youtube/grl-youtube.h
deleted file mode 100644
index dc88589..0000000
--- a/src/youtube/grl-youtube.h
+++ /dev/null
@@ -1,74 +0,0 @@
-/*
- * Copyright (C) 2010 Igalia S.L.
- *
- * Contact: Iago Toral Quiroga <itoral igalia 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_YOUTUBE_SOURCE_H_
-#define _GRL_YOUTUBE_SOURCE_H_
-
-#include <grilo.h>
-
-#define GRL_YOUTUBE_SOURCE_TYPE \
- (grl_youtube_source_get_type ())
-
-#define GRL_YOUTUBE_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_CAST ((obj), \
- GRL_YOUTUBE_SOURCE_TYPE, \
- GrlYoutubeSource))
-
-#define GRL_IS_YOUTUBE_SOURCE(obj) \
- (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
- GRL_YOUTUBE_SOURCE_TYPE))
-
-#define GRL_YOUTUBE_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_CAST((klass), \
- GRL_YOUTUBE_SOURCE_TYPE, \
- GrlYoutubeSourceClass))
-
-#define GRL_IS_YOUTUBE_SOURCE_CLASS(klass) \
- (G_TYPE_CHECK_CLASS_TYPE((klass), \
- GRL_YOUTUBE_SOURCE_TYPE))
-
-#define GRL_YOUTUBE_SOURCE_GET_CLASS(obj) \
- (G_TYPE_INSTANCE_GET_CLASS ((obj), \
- GRL_YOUTUBE_SOURCE_TYPE, \
- GrlYoutubeSourceClass))
-
-typedef struct _GrlYoutubeSource GrlYoutubeSource;
-typedef struct _GrlYoutubeSourcePriv GrlYoutubeSourcePriv;
-
-struct _GrlYoutubeSource {
-
- GrlMediaSource parent;
- GrlYoutubeSourcePriv *priv;
-
-};
-
-typedef struct _GrlYoutubeSourceClass GrlYoutubeSourceClass;
-
-struct _GrlYoutubeSourceClass {
-
- GrlMediaSourceClass parent_class;
-
-};
-
-GType grl_youtube_source_get_type (void);
-
-#endif /* _GRL_YOUTUBE_SOURCE_H_ */
diff --git a/src/youtube/grl-youtube.xml b/src/youtube/grl-youtube.xml
deleted file mode 100644
index b874678..0000000
--- a/src/youtube/grl-youtube.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-<plugin>
- <info>
- <name>YouTube</name>
- <description>A plugin for browsing and searching Youtube videos</description>
- <author>Igalia S.L.</author>
- <license>LGPL</license>
- <site>http://www.igalia.com</site>
- </info>
-</plugin>
--
1.7.1
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]