[rhythmbox] podcast: add podcast search infrastructure
- From: Jonathan Matthew <jmatthew src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rhythmbox] podcast: add podcast search infrastructure
- Date: Sat, 26 May 2012 04:23:49 +0000 (UTC)
commit 539e378a51ed4af0fe0955b531af3e2d615d55ad
Author: Jonathan Matthew <jonathan d14n org>
Date: Sat May 26 13:45:23 2012 +1000
podcast: add podcast search infrastructure
This searches iTunes and Miroguide for podcast feeds. Not hooked
up to any UI yet, but will be soon.
This also makes json-glib a required dependency, which means the
last.fm plugin can always be built.
.gitignore | 1 +
configure.ac | 33 +-----
plugins/Makefile.am | 5 +-
podcast/Makefile.am | 26 ++++-
podcast/rb-podcast-manager.c | 31 +++++
podcast/rb-podcast-manager.h | 2 +
podcast/rb-podcast-parse.c | 47 +++++++
podcast/rb-podcast-parse.h | 4 +
podcast/rb-podcast-search-itunes.c | 207 +++++++++++++++++++++++++++++++
podcast/rb-podcast-search-miroguide.c | 219 +++++++++++++++++++++++++++++++++
podcast/rb-podcast-search.c | 96 ++++++++++++++
podcast/rb-podcast-search.h | 74 +++++++++++
podcast/test-podcast-search.c | 152 +++++++++++++++++++++++
13 files changed, 859 insertions(+), 38 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 57d9c5c..73c80e0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -83,6 +83,7 @@ po/.intltool-merge-cache
#
podcast/test-podcast-parse
+podcast/test-podcast-search
#
remote/dbus/rhythmbox-client
diff --git a/configure.ac b/configure.ac
index 3644d32..b3a4f31 100644
--- a/configure.ac
+++ b/configure.ac
@@ -101,7 +101,8 @@ PKG_CHECK_MODULES(RHYTHMBOX, \
libpeas-1.0 >= $LIBPEAS_REQS \
libpeas-gtk-1.0 >= $LIBPEAS_REQS \
libxml-2.0 >= $LIBXML2_REQS \
- tdb >= 1.2.6)
+ tdb >= 1.2.6 \
+ json-glib-1.0)
PKG_CHECK_MODULES(TOTEM_PLPARSER, totem-plparser >= $TOTEM_PLPARSER_REQS, have_totem_plparser=yes, have_totem_plparser=no)
if test x$have_totem_plparser != xyes; then
@@ -768,30 +769,6 @@ AC_SUBST(CLUTTER_CFLAGS)
AC_SUBST(CLUTTER_LIBS)
dnl ================================================================
-dnl Dependencies for Last.fm plugin
-dnl ================================================================
-AC_ARG_ENABLE(lastfm,
- AC_HELP_STRING([--disable-lastfm],
- [Disable Last.fm support]),,
- enable_lastfm=auto)
-if test "x$enable_lastfm" != "xno"; then
- PKG_CHECK_MODULES(JSON_GLIB, json-glib-1.0,
- have_json_glib=yes,
- have_json_glib=no)
- if test "x$have_json_glib" = "xno" -a "x$enable_lastfm" = "xyes"; then
- AC_MSG_ERROR([Last.fm support explicitly requested, but json-glib couldn't be found])
- fi
- if test x"$have_json_glib" = "xyes"; then
- AC_DEFINE(HAVE_JSON_GLIB, 1, [Define if json-glib support is enabled])
- fi
-fi
-
-AM_CONDITIONAL(ENABLE_LASTFM, test x"$have_json_glib" = "xyes")
-
-AC_SUBST(JSON_GLIB_CFLAGS)
-AC_SUBST(JSON_GLIB_LIBS)
-
-dnl ================================================================
dnl grilo plugin
dnl ================================================================
AC_ARG_ENABLE(grilo,
@@ -999,10 +976,4 @@ else
AC_MSG_NOTICE([ Visualizer plugin disabled])
fi
-if test "x$have_json_glib" = xyes; then
- AC_MSG_NOTICE([** Last.fm support enabled])
-else
- AC_MSG_NOTICE([ Last.fm support disabled])
-fi
-
AC_MSG_NOTICE([End options])
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index dcc449e..bbea476 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -3,6 +3,7 @@ plugininclude_HEADERS = rb-plugin-macros.h
SUBDIRS = \
audiocd \
+ audioscrobbler \
dbus-media-server \
generic-player \
iradio \
@@ -60,10 +61,6 @@ if USE_CLUTTER
SUBDIRS += visualizer
endif
-if ENABLE_LASTFM
-SUBDIRS += audioscrobbler
-endif
-
if USE_NOTIFY
SUBDIRS += notification
endif
diff --git a/podcast/Makefile.am b/podcast/Makefile.am
index 5a9fe99..18cf01f 100644
--- a/podcast/Makefile.am
+++ b/podcast/Makefile.am
@@ -4,12 +4,16 @@ podcastincludedir = $(includedir)/rhythmbox/podcast
podcastinclude_HEADERS = \
rb-podcast-entry-types.h \
rb-podcast-parse.h \
- rb-podcast-manager.h
+ rb-podcast-manager.h \
+ rb-podcast-search.h
librbpodcast_parse_la_SOURCES = \
rb-podcast-parse.c \
- rb-podcast-parse.h
+ rb-podcast-parse.h \
+ rb-podcast-search.c \
+ rb-podcast-search-itunes.c \
+ rb-podcast-search-miroguide.c
librbpodcast_la_SOURCES = \
$(podcastinclude_HEADERS) \
@@ -19,6 +23,9 @@ librbpodcast_la_SOURCES = \
rb-podcast-properties-dialog.h \
rb-podcast-main-source.c \
rb-podcast-main-source.h \
+ rb-podcast-search.c \
+ rb-podcast-search-itunes.c \
+ rb-podcast-search-miroguide.c \
rb-podcast-settings.h \
rb-podcast-source.c \
rb-podcast-source.h \
@@ -26,7 +33,18 @@ librbpodcast_la_SOURCES = \
rb-podcast-manager.c \
rb-podcast-entry-types.c
-noinst_PROGRAMS = test-podcast-parse
+noinst_PROGRAMS = test-podcast-parse test-podcast-search
+
+test_podcast_search_SOURCES = \
+ test-podcast-search.c
+test_podcast_search_LDADD = \
+ librbpodcast_parse.la \
+ $(top_builddir)/lib/librb.la \
+ $(RHYTHMBOX_LIBS) \
+ $(JSON_GLIB_LIBS) \
+ $(TOTEM_PLPARSER_LIBS)
+
+
test_podcast_parse_SOURCES = \
test-podcast-parse.c
test_podcast_parse_LDADD = \
@@ -48,9 +66,11 @@ AM_CFLAGS = \
-I$(top_builddir)/lib \
$(RHYTHMBOX_CFLAGS) \
$(WEBKIT_CFLAGS) \
+ $(JSON_GLIB_CFLAGS) \
$(TOTEM_PLPARSER_CFLAGS)
librbpodcast_la_LDFLAGS = -export-dynamic
+librbpodcast_la_LIBADD = $(JSON_GLIB_LIBS)
PLUGIN_FILES = rhythmbox-itms-plugin.c npapi.h npupp.h npruntime.h
diff --git a/podcast/rb-podcast-manager.c b/podcast/rb-podcast-manager.c
index 88df223..72b8b87 100644
--- a/podcast/rb-podcast-manager.c
+++ b/podcast/rb-podcast-manager.c
@@ -40,6 +40,7 @@
#include "rb-podcast-settings.h"
#include "rb-podcast-manager.h"
#include "rb-podcast-entry-types.h"
+#include "rb-podcast-search.h"
#include "rb-file-helpers.h"
#include "rb-debug.h"
#include "rb-marshal.h"
@@ -115,6 +116,7 @@ struct RBPodcastManagerPrivate
gboolean shutdown;
RBExtDB *art_store;
+ GList *searches;
GSettings *settings;
GFile *timestamp_file;
};
@@ -273,6 +275,10 @@ rb_podcast_manager_constructed (GObject *object)
RB_CHAIN_GOBJECT_METHOD (rb_podcast_manager_parent_class, constructed, object);
+ /* add built in search types */
+ rb_podcast_manager_add_search (pd, rb_podcast_search_itunes_get_type ());
+ rb_podcast_manager_add_search (pd, rb_podcast_search_miroguide_get_type ());
+
pd->priv->settings = g_settings_new (PODCAST_SETTINGS_SCHEMA);
g_signal_connect_object (pd->priv->settings,
"changed",
@@ -354,6 +360,8 @@ rb_podcast_manager_finalize (GObject *object)
g_list_free (pd->priv->download_list);
}
+ g_list_free (pd->priv->searches);
+
G_OBJECT_CLASS (rb_podcast_manager_parent_class)->finalize (object);
}
@@ -2232,3 +2240,26 @@ rb_podcast_manager_get_podcast_dir (RBPodcastManager *pd)
return conf_dir_uri;
}
+void
+rb_podcast_manager_add_search (RBPodcastManager *pd, GType search_type)
+{
+ pd->priv->searches = g_list_append (pd->priv->searches, GUINT_TO_POINTER (search_type));
+}
+
+GList *
+rb_podcast_manager_get_searches (RBPodcastManager *pd)
+{
+ GList *searches = NULL;
+ GList *i;
+
+ for (i = pd->priv->searches; i != NULL; i = i->next) {
+ RBPodcastSearch *search;
+ GType search_type;
+
+ search_type = GPOINTER_TO_UINT (i->data);
+ search = RB_PODCAST_SEARCH (g_object_new (search_type, NULL));
+ searches = g_list_append (searches, search);
+ }
+
+ return searches;
+}
diff --git a/podcast/rb-podcast-manager.h b/podcast/rb-podcast-manager.h
index cc2320f..5fcf8ef 100644
--- a/podcast/rb-podcast-manager.h
+++ b/podcast/rb-podcast-manager.h
@@ -93,6 +93,8 @@ RhythmDBEntry * rb_podcast_manager_add_post (RhythmDB *db,
gboolean rb_podcast_manager_entry_downloaded (RhythmDBEntry *entry);
gboolean rb_podcast_manager_entry_in_download_queue (RBPodcastManager *pd, RhythmDBEntry *entry);
+void rb_podcast_manager_add_search (RBPodcastManager *pd, GType search_type);
+GList * rb_podcast_manager_get_searches (RBPodcastManager *pd);
G_END_DECLS
diff --git a/podcast/rb-podcast-parse.c b/podcast/rb-podcast-parse.c
index 77c7141..e81e513 100644
--- a/podcast/rb-podcast-parse.c
+++ b/podcast/rb-podcast-parse.c
@@ -242,6 +242,37 @@ rb_podcast_parse_load_feed (RBPodcastChannel *data,
return TRUE;
}
+RBPodcastChannel *
+rb_podcast_parse_channel_copy (RBPodcastChannel *data)
+{
+ RBPodcastChannel *copy;
+ copy = g_new0 (RBPodcastChannel, 1);
+ copy->url = g_strdup (data->url);
+ copy->title = g_strdup (data->title);
+ copy->lang = g_strdup (data->lang);
+ copy->description = g_strdup (data->description);
+ copy->author = g_strdup (data->author);
+ copy->contact = g_strdup (data->contact);
+ copy->img = g_strdup (data->img);
+ copy->pub_date = data->pub_date;
+ copy->copyright = g_strdup (data->copyright);
+ copy->is_opml = data->is_opml;
+
+ if (data->posts != NULL) {
+ GList *l;
+ for (l = data->posts; l != NULL; l = l->next) {
+ RBPodcastItem *copyitem;
+ copyitem = rb_podcast_parse_item_copy (l->data);
+ data->posts = g_list_prepend (data->posts, copyitem);
+ }
+ data->posts = g_list_reverse (data->posts);
+ } else {
+ copy->num_posts = data->num_posts;
+ }
+
+ return copy;
+}
+
void
rb_podcast_parse_channel_free (RBPodcastChannel *data)
{
@@ -264,6 +295,21 @@ rb_podcast_parse_channel_free (RBPodcastChannel *data)
data = NULL;
}
+RBPodcastItem *
+rb_podcast_parse_item_copy (RBPodcastItem *item)
+{
+ RBPodcastItem *copy;
+ copy = g_new0 (RBPodcastItem, 1);
+ copy->title = g_strdup (item->title);
+ copy->url = g_strdup (item->url);
+ copy->description = g_strdup (item->description);
+ copy->author = g_strdup (item->author);
+ copy->pub_date = item->pub_date;
+ copy->duration = item->duration;
+ copy->filesize = item->filesize;
+ return copy;
+}
+
void
rb_podcast_parse_item_free (RBPodcastItem *item)
{
@@ -272,6 +318,7 @@ rb_podcast_parse_item_free (RBPodcastItem *item)
g_free (item->title);
g_free (item->url);
g_free (item->description);
+ g_free (item->author);
g_free (item);
}
diff --git a/podcast/rb-podcast-parse.h b/podcast/rb-podcast-parse.h
index 26eaafb..136dea4 100644
--- a/podcast/rb-podcast-parse.h
+++ b/podcast/rb-podcast-parse.h
@@ -67,12 +67,16 @@ typedef struct
gboolean is_opml;
GList *posts;
+ int num_posts;
} RBPodcastChannel;
gboolean rb_podcast_parse_load_feed (RBPodcastChannel *data,
const char *url,
gboolean existing_feed,
GError **error);
+
+RBPodcastChannel *rb_podcast_parse_channel_copy (RBPodcastChannel *data);
+RBPodcastItem *rb_podcast_parse_item_copy (RBPodcastItem *data);
void rb_podcast_parse_channel_free (RBPodcastChannel *data);
void rb_podcast_parse_item_free (RBPodcastItem *data);
diff --git a/podcast/rb-podcast-search-itunes.c b/podcast/rb-podcast-search-itunes.c
new file mode 100644
index 0000000..421a92d
--- /dev/null
+++ b/podcast/rb-podcast-search-itunes.c
@@ -0,0 +1,207 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Jonathan Matthew <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "config.h"
+
+#include "rb-podcast-search.h"
+#include "rb-debug.h"
+
+#include <libsoup/soup.h>
+#include <libsoup/soup-gnome.h>
+#include <json-glib/json-glib.h>
+
+#define RB_TYPE_PODCAST_SEARCH_ITUNES (rb_podcast_search_itunes_get_type ())
+#define RB_PODCAST_SEARCH_ITUNES(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_PODCAST_SEARCH_ITUNES, RBPodcastSearchITunes))
+#define RB_PODCAST_SEARCH_ITUNES_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_PODCAST_SEARCH_ITUNES, RBPodcastSearchITunesClass))
+#define RB_IS_PODCAST_SEARCH_ITUNES(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_PODCAST_SEARCH_ITUNES))
+#define RB_IS_PODCAST_SEARCH_ITUNES_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_PODCAST_SEARCH_ITUNES))
+#define RB_PODCAST_SEARCH_ITUNES_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_PODCAST_SEARCH_ITUNES, RBPodcastSearchITunesClass))
+
+typedef struct _RBPodcastSearchITunes RBPodcastSearchITunes;
+typedef struct _RBPodcastSearchITunesClass RBPodcastSearchITunesClass;
+
+struct _RBPodcastSearchITunes
+{
+ RBPodcastSearch parent;
+
+ SoupSession *session;
+};
+
+struct _RBPodcastSearchITunesClass
+{
+ RBPodcastSearchClass parent;
+};
+
+static void rb_podcast_search_itunes_class_init (RBPodcastSearchITunesClass *klass);
+static void rb_podcast_search_itunes_init (RBPodcastSearchITunes *search);
+
+G_DEFINE_TYPE (RBPodcastSearchITunes, rb_podcast_search_itunes, RB_TYPE_PODCAST_SEARCH);
+
+#define ITUNES_SEARCH_URI "http://itunes.apple.com/WebObjects/MZStoreServices.woa/ws/wsSearch"
+
+static void
+process_results (RBPodcastSearchITunes *search, JsonParser *parser)
+{
+ JsonObject *container;
+ JsonArray *results;
+ guint i;
+
+ container = json_node_get_object (json_parser_get_root (parser));
+ results = json_node_get_array (json_object_get_member (container, "results"));
+
+ for (i = 0; i < json_array_get_length (results); i++) {
+ JsonObject *feed;
+ RBPodcastChannel *channel;
+
+ feed = json_array_get_object_element (results, i);
+
+ /* check wrapperType==track, kind==podcast ? */
+
+ channel = g_new0 (RBPodcastChannel, 1);
+
+ channel->url = g_strdup (json_object_get_string_member (feed, "collectionViewUrl"));
+ channel->title = g_strdup (json_object_get_string_member (feed, "collectionName"));
+ channel->author = g_strdup (json_object_get_string_member (feed, "artistName"));
+ channel->img = g_strdup (json_object_get_string_member (feed, "artworkUrl100")); /* 100? */
+ channel->is_opml = FALSE;
+
+ channel->num_posts = json_object_get_int_member (feed, "trackCount");
+
+ rb_debug ("got result %s (%s)", channel->title, channel->url);
+ rb_podcast_search_result (RB_PODCAST_SEARCH (search), channel);
+ rb_podcast_parse_channel_free (channel);
+ }
+}
+
+static void
+search_response_cb (SoupSession *session, SoupMessage *msg, RBPodcastSearchITunes *search)
+{
+ JsonParser *parser;
+ GError *error = NULL;
+ int code;
+
+ g_object_get (msg, SOUP_MESSAGE_STATUS_CODE, &code, NULL);
+ if (code != 200) {
+ char *reason;
+
+ g_object_get (msg, SOUP_MESSAGE_REASON_PHRASE, &reason, NULL);
+ rb_debug ("search request failed: %s", reason);
+ g_free (reason);
+ rb_podcast_search_finished (RB_PODCAST_SEARCH (search), FALSE);
+ return;
+ }
+
+ if (msg->response_body->data == NULL) {
+ rb_debug ("no response data");
+ rb_podcast_search_finished (RB_PODCAST_SEARCH (search), TRUE);
+ return;
+ }
+
+ parser = json_parser_new ();
+ if (json_parser_load_from_data (parser, msg->response_body->data, msg->response_body->length, &error)) {
+ process_results (search, parser);
+ } else {
+ rb_debug ("unable to parse response data: %s", error->message);
+ g_clear_error (&error);
+ }
+
+ g_object_unref (parser);
+ rb_podcast_search_finished (RB_PODCAST_SEARCH (search), TRUE);
+}
+
+static void
+impl_start (RBPodcastSearch *bsearch, const char *text, int max_results)
+{
+ SoupURI *uri;
+ SoupMessage *message;
+ char *limit;
+ RBPodcastSearchITunes *search = RB_PODCAST_SEARCH_ITUNES (bsearch);
+
+ search->session = soup_session_async_new_with_options (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
+ SOUP_TYPE_GNOME_FEATURES_2_26,
+ NULL);
+
+ uri = soup_uri_new (ITUNES_SEARCH_URI);
+ limit = g_strdup_printf ("%d", max_results);
+ soup_uri_set_query_from_fields (uri,
+ "term", text,
+ "media", "podcast",
+ "entity", "podcast",
+ "limit", limit,
+ "version", "2",
+ "output", "json",
+ NULL);
+ g_free (limit);
+
+ message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+ soup_uri_free (uri);
+
+ soup_session_queue_message (search->session, message, (SoupSessionCallback) search_response_cb, search);
+}
+
+static void
+impl_cancel (RBPodcastSearch *bsearch)
+{
+ RBPodcastSearchITunes *search = RB_PODCAST_SEARCH_ITUNES (bsearch);
+
+ if (search->session != NULL) {
+ soup_session_abort (search->session);
+ }
+}
+
+static void
+impl_dispose (GObject *object)
+{
+ RBPodcastSearchITunes *search = RB_PODCAST_SEARCH_ITUNES (object);
+
+ if (search->session != NULL) {
+ soup_session_abort (search->session);
+ g_object_unref (search->session);
+ search->session = NULL;
+ }
+
+ G_OBJECT_CLASS (rb_podcast_search_itunes_parent_class)->dispose (object);
+}
+
+static void
+rb_podcast_search_itunes_init (RBPodcastSearchITunes *search)
+{
+ /* do nothing? */
+}
+
+static void
+rb_podcast_search_itunes_class_init (RBPodcastSearchITunesClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ RBPodcastSearchClass *search_class = RB_PODCAST_SEARCH_CLASS (klass);
+
+ object_class->dispose = impl_dispose;
+
+ search_class->cancel = impl_cancel;
+ search_class->start = impl_start;
+}
diff --git a/podcast/rb-podcast-search-miroguide.c b/podcast/rb-podcast-search-miroguide.c
new file mode 100644
index 0000000..d7fd519
--- /dev/null
+++ b/podcast/rb-podcast-search-miroguide.c
@@ -0,0 +1,219 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Jonathan Matthew <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "config.h"
+
+#include "rb-podcast-search.h"
+#include "rb-debug.h"
+
+#include <libsoup/soup.h>
+#include <libsoup/soup-gnome.h>
+#include <json-glib/json-glib.h>
+#include <totem-pl-parser.h>
+
+#define RB_TYPE_PODCAST_SEARCH_MIROGUIDE (rb_podcast_search_miroguide_get_type ())
+#define RB_PODCAST_SEARCH_MIROGUIDE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_PODCAST_SEARCH_MIROGUIDE, RBPodcastSearchMiroGuide))
+#define RB_PODCAST_SEARCH_MIROGUIDE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_PODCAST_SEARCH_MIROGUIDE, RBPodcastSearchMiroGuideClass))
+#define RB_IS_PODCAST_SEARCH_MIROGUIDE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_PODCAST_SEARCH_MIROGUIDE))
+#define RB_IS_PODCAST_SEARCH_MIROGUIDE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_PODCAST_SEARCH_MIROGUIDE))
+#define RB_PODCAST_SEARCH_MIROGUIDE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_PODCAST_SEARCH_MIROGUIDE, RBPodcastSearchMiroGuideClass))
+
+typedef struct _RBPodcastSearchMiroGuide RBPodcastSearchMiroGuide;
+typedef struct _RBPodcastSearchMiroGuideClass RBPodcastSearchMiroGuideClass;
+
+struct _RBPodcastSearchMiroGuide
+{
+ RBPodcastSearch parent;
+
+ SoupSession *session;
+};
+
+struct _RBPodcastSearchMiroGuideClass
+{
+ RBPodcastSearchClass parent;
+};
+
+static void rb_podcast_search_miroguide_class_init (RBPodcastSearchMiroGuideClass *klass);
+static void rb_podcast_search_miroguide_init (RBPodcastSearchMiroGuide *search);
+
+G_DEFINE_TYPE (RBPodcastSearchMiroGuide, rb_podcast_search_miroguide, RB_TYPE_PODCAST_SEARCH);
+
+#define MIROGUIDE_SEARCH_URI "http://www.miroguide.com/api/get_feeds"
+
+static void
+process_results (RBPodcastSearchMiroGuide *search, JsonParser *parser)
+{
+ JsonArray *results;
+ guint i;
+
+ results = json_node_get_array (json_parser_get_root (parser));
+
+ for (i = 0; i < json_array_get_length (results); i++) {
+ JsonObject *feed;
+ JsonArray *items;
+ RBPodcastChannel *channel;
+ int j;
+
+ feed = json_array_get_object_element (results, i);
+
+ channel = g_new0 (RBPodcastChannel, 1);
+ channel->url = g_strdup (json_object_get_string_member (feed, "url"));
+ channel->title = g_strdup (json_object_get_string_member (feed, "name"));
+ channel->author = g_strdup (json_object_get_string_member (feed, "publisher")); /* hrm */
+ channel->img = g_strdup (json_object_get_string_member (feed, "thumbnail_url"));
+ channel->is_opml = FALSE;
+ rb_debug ("feed %d: url %s, name \"%s\"", i, channel->url, channel->title);
+
+ items = json_object_get_array_member (feed, "item");
+ for (j = 0; j < json_array_get_length (items); j++) {
+ JsonObject *episode = json_array_get_object_element (items, j);
+ RBPodcastItem *item;
+
+ item = g_new0 (RBPodcastItem, 1);
+ item->title = g_strdup (json_object_get_string_member (episode, "name"));
+ item->url = g_strdup (json_object_get_string_member (episode, "url"));
+ item->description = g_strdup (json_object_get_string_member (episode, "description"));
+ item->pub_date = totem_pl_parser_parse_date (json_object_get_string_member (episode, "date"), FALSE);
+ item->filesize = json_object_get_int_member (episode, "size");
+ rb_debug ("item %d: title \"%s\", url %s", j, item->title, item->url);
+
+ channel->posts = g_list_prepend (channel->posts, item);
+ }
+ channel->posts = g_list_reverse (channel->posts);
+ rb_debug ("finished parsing items");
+
+ rb_podcast_search_result (RB_PODCAST_SEARCH (search), channel);
+ rb_podcast_parse_channel_free (channel);
+ }
+}
+
+static void
+search_response_cb (SoupSession *session, SoupMessage *msg, RBPodcastSearchMiroGuide *search)
+{
+ JsonParser *parser;
+ int code;
+
+ g_object_get (msg, SOUP_MESSAGE_STATUS_CODE, &code, NULL);
+ if (code != 200) {
+ char *reason;
+
+ g_object_get (msg, SOUP_MESSAGE_REASON_PHRASE, &reason, NULL);
+ rb_debug ("search request failed: %s", reason);
+ g_free (reason);
+ rb_podcast_search_finished (RB_PODCAST_SEARCH (search), FALSE);
+ return;
+ }
+
+ if (msg->response_body->data == NULL) {
+ rb_debug ("no response data");
+ rb_podcast_search_finished (RB_PODCAST_SEARCH (search), TRUE);
+ return;
+ }
+
+ parser = json_parser_new ();
+ if (json_parser_load_from_data (parser, msg->response_body->data, msg->response_body->length, NULL)) {
+ process_results (search, parser);
+ } else {
+ rb_debug ("unable to parse response data");
+ }
+
+ g_object_unref (parser);
+ rb_podcast_search_finished (RB_PODCAST_SEARCH (search), TRUE);
+}
+
+static void
+impl_start (RBPodcastSearch *bsearch, const char *text, int max_results)
+{
+ SoupURI *uri;
+ SoupMessage *message;
+ char *limit;
+ RBPodcastSearchMiroGuide *search = RB_PODCAST_SEARCH_MIROGUIDE (bsearch);
+
+ search->session = soup_session_async_new_with_options (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
+ SOUP_TYPE_GNOME_FEATURES_2_26,
+ NULL);
+
+ uri = soup_uri_new (MIROGUIDE_SEARCH_URI);
+ limit = g_strdup_printf ("%d", max_results);
+ soup_uri_set_query_from_fields (uri,
+ "filter", "audio",
+ "filter_value", "1",
+ "filter", "name",
+ "filter_value", text,
+ "sort", "popular", /* hmm */
+ "limit", limit,
+ "datatype", "json",
+ NULL);
+ g_free (limit);
+
+ message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+ soup_uri_free (uri);
+
+ soup_session_queue_message (search->session, message, (SoupSessionCallback) search_response_cb, search);
+}
+
+static void
+impl_cancel (RBPodcastSearch *bsearch)
+{
+ RBPodcastSearchMiroGuide *search = RB_PODCAST_SEARCH_MIROGUIDE (bsearch);
+ if (search->session != NULL) {
+ soup_session_abort (search->session);
+ }
+}
+
+static void
+impl_dispose (GObject *object)
+{
+ RBPodcastSearchMiroGuide *search = RB_PODCAST_SEARCH_MIROGUIDE (object);
+
+ if (search->session != NULL) {
+ soup_session_abort (search->session);
+ g_object_unref (search->session);
+ search->session = NULL;
+ }
+
+ G_OBJECT_CLASS (rb_podcast_search_miroguide_parent_class)->dispose (object);
+}
+
+static void
+rb_podcast_search_miroguide_init (RBPodcastSearchMiroGuide *search)
+{
+ /* do nothing? */
+}
+
+static void
+rb_podcast_search_miroguide_class_init (RBPodcastSearchMiroGuideClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ RBPodcastSearchClass *search_class = RB_PODCAST_SEARCH_CLASS (klass);
+
+ object_class->dispose = impl_dispose;
+
+ search_class->start = impl_start;
+ search_class->cancel = impl_cancel;
+}
diff --git a/podcast/rb-podcast-search.c b/podcast/rb-podcast-search.c
new file mode 100644
index 0000000..0a963f1
--- /dev/null
+++ b/podcast/rb-podcast-search.c
@@ -0,0 +1,96 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2010 Jonathan Matthew <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#include "config.h"
+
+#include "rb-podcast-search.h"
+
+static void rb_podcast_search_class_init (RBPodcastSearchClass *klass);
+static void rb_podcast_search_init (RBPodcastSearch *search);
+
+enum {
+ RESULT,
+ FINISHED,
+ LAST_SIGNAL
+};
+
+G_DEFINE_TYPE (RBPodcastSearch, rb_podcast_search, G_TYPE_OBJECT);
+
+static guint signals[LAST_SIGNAL];
+
+void
+rb_podcast_search_start (RBPodcastSearch *search, const char *text, int max_results)
+{
+ RBPodcastSearchClass *klass = RB_PODCAST_SEARCH_GET_CLASS (search);
+ klass->start (search, text, max_results);
+}
+
+void
+rb_podcast_search_cancel (RBPodcastSearch *search)
+{
+ RBPodcastSearchClass *klass = RB_PODCAST_SEARCH_GET_CLASS (search);
+ klass->cancel (search);
+}
+
+void
+rb_podcast_search_result (RBPodcastSearch *search, RBPodcastChannel *data)
+{
+ g_signal_emit (search, signals[RESULT], 0, data);
+}
+
+void
+rb_podcast_search_finished (RBPodcastSearch *search, gboolean successful)
+{
+ g_signal_emit (search, signals[FINISHED], 0, successful);
+}
+
+static void
+rb_podcast_search_init (RBPodcastSearch *search)
+{
+}
+
+static void
+rb_podcast_search_class_init (RBPodcastSearchClass *klass)
+{
+ signals[RESULT] = g_signal_new ("result",
+ RB_TYPE_PODCAST_SEARCH,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE,
+ 1, G_TYPE_POINTER);
+ signals[FINISHED] = g_signal_new ("finished",
+ RB_TYPE_PODCAST_SEARCH,
+ G_SIGNAL_RUN_LAST,
+ 0,
+ NULL, NULL,
+ g_cclosure_marshal_VOID__BOOLEAN,
+ G_TYPE_NONE,
+ 1, G_TYPE_BOOLEAN);
+}
diff --git a/podcast/rb-podcast-search.h b/podcast/rb-podcast-search.h
new file mode 100644
index 0000000..91e09e2
--- /dev/null
+++ b/podcast/rb-podcast-search.h
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2010 Jonathan Matthew <jonathan d14n org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+#ifndef RB_PODCAST_SEARCH_H
+#define RB_PODCAST_SEARCH_H
+
+#include <glib-object.h>
+
+#include "rb-podcast-parse.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_PODCAST_SEARCH (rb_podcast_search_get_type ())
+#define RB_PODCAST_SEARCH(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_PODCAST_SEARCH, RBPodcastSearch))
+#define RB_PODCAST_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_PODCAST_SEARCH, RBPodcastSearchClass))
+#define RB_IS_PODCAST_SEARCH(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_PODCAST_SEARCH))
+#define RB_IS_PODCAST_SEARCH_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_PODCAST_SEARCH))
+#define RB_PODCAST_SEARCH_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_PODCAST_SEARCH, RBPodcastSearchClass))
+
+typedef struct _RBPodcastSearch RBPodcastSearch;
+typedef struct _RBPodcastSearchClass RBPodcastSearchClass;
+
+struct _RBPodcastSearch
+{
+ GObject parent;
+};
+
+struct _RBPodcastSearchClass
+{
+ GObjectClass parent;
+
+ /* methods */
+ void (*start) (RBPodcastSearch *search, const char *text, int max_results);
+ void (*cancel) (RBPodcastSearch *search);
+};
+
+GType rb_podcast_search_get_type (void);
+
+void rb_podcast_search_start (RBPodcastSearch *search, const char *text, int max_results);
+void rb_podcast_search_cancel (RBPodcastSearch *search);
+
+void rb_podcast_search_result (RBPodcastSearch *search, RBPodcastChannel *data);
+void rb_podcast_search_finished (RBPodcastSearch *search, gboolean successful);
+
+/* built in search types */
+
+GType rb_podcast_search_itunes_get_type (void);
+GType rb_podcast_search_miroguide_get_type (void);
+
+#endif /* RB_PODCAST_SEARCH_H */
diff --git a/podcast/test-podcast-search.c b/podcast/test-podcast-search.c
new file mode 100644
index 0000000..bd0ca43
--- /dev/null
+++ b/podcast/test-podcast-search.c
@@ -0,0 +1,152 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * The Rhythmbox authors hereby grant permission for non-GPL compatible
+ * GStreamer plugins to be used and distributed together with GStreamer
+ * and Rhythmbox. This permission is above and beyond the permissions granted
+ * by the GPL license by which Rhythmbox is covered. If you modify this code
+ * you may extend this exception to your version of the code, but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include "config.h"
+
+#include <locale.h>
+#include <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n.h>
+
+#include "rb-podcast-search.h"
+
+#include <string.h>
+
+static gboolean debug = FALSE;
+static int done = 0;
+
+void rb_debug_realf (const char *func,
+ const char *file,
+ int line,
+ gboolean newline,
+ const char *format, ...) G_GNUC_PRINTF (5, 6);
+
+/* For the benefit of the podcast parsing code */
+void
+rb_debug_realf (const char *func,
+ const char *file,
+ int line,
+ gboolean newline,
+ const char *format, ...)
+{
+ va_list args;
+ char buffer[1025];
+
+ if (debug == FALSE)
+ return;
+
+ va_start (args, format);
+ g_vsnprintf (buffer, 1024, format, args);
+ va_end (args);
+
+ g_printerr (newline ? "%s:%d [%s] %s\n" : "%s:%d [%s] %s",
+ file, line, func, buffer);
+}
+
+static void
+result_cb (RBPodcastSearch *search, RBPodcastChannel *data)
+{
+ char datebuf[1025];
+ GDate date;
+
+ g_date_set_time_t (&date, data->pub_date);
+ g_date_strftime (datebuf, 1024, "%F %T", &date);
+
+ g_print ("Result from %s\n", G_OBJECT_TYPE_NAME (search));
+
+ g_print ("Podcast title: %s\n", data->title);
+ g_print ("Description: %s\n", data->description);
+ g_print ("Author: %s\n", data->author);
+ g_print ("Date: %s\n", datebuf);
+
+ if (data->num_posts > 0) {
+ g_print ("Number of episodes: %d\n", data->num_posts);
+ g_print ("\n");
+ } else {
+ GList *l;
+ g_print ("Number of episodes: %d\n", g_list_length (data->posts));
+ g_print ("\n");
+ for (l = data->posts; l != NULL; l = l->next) {
+ RBPodcastItem *item = l->data;
+
+ g_date_set_time_t (&date, item->pub_date);
+ g_date_strftime (datebuf, 1024, "%F %T", &date);
+
+ g_print ("\tItem title: %s\n", item->title);
+ g_print ("\tURL: %s\n", item->url);
+ g_print ("\tAuthor: %s\n", item->author);
+ g_print ("\tDate: %s\n", datebuf);
+ g_print ("\tDescription: %s\n", item->description);
+ g_print ("\n");
+ }
+ }
+}
+
+static void
+finished_cb (RBPodcastSearch *search, GMainLoop *loop)
+{
+ g_print ("Search %s finished\n", G_OBJECT_TYPE_NAME (search));
+ done++;
+ if (done == 2) {
+ g_main_loop_quit (loop);
+ }
+}
+
+int main (int argc, char **argv)
+{
+ GMainLoop *loop;
+ RBPodcastSearch *itunes;
+ RBPodcastSearch *miroguide;
+ char *text;
+
+ g_type_init ();
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+
+ text = argv[1];
+ if (argv[2] != NULL && strcmp (argv[2], "--debug") == 0) {
+ debug = TRUE;
+ }
+
+ loop = g_main_loop_new (NULL, FALSE);
+
+ itunes = RB_PODCAST_SEARCH (g_object_new (rb_podcast_search_itunes_get_type (), NULL));
+ miroguide = RB_PODCAST_SEARCH (g_object_new (rb_podcast_search_miroguide_get_type (), NULL));
+
+ g_signal_connect (itunes, "result", G_CALLBACK (result_cb), NULL);
+ g_signal_connect (miroguide, "result", G_CALLBACK (result_cb), NULL);
+ g_signal_connect (itunes, "finished", G_CALLBACK (finished_cb), loop);
+ g_signal_connect (miroguide, "finished", G_CALLBACK (finished_cb), loop);
+
+ rb_podcast_search_start (itunes, text, 10);
+ rb_podcast_search_start (miroguide, text, 10);
+
+ g_main_loop_run (loop);
+
+ return 0;
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]