[rhythmbox] add grilo plugin
- From: Jonathan Matthew <jmatthew src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [rhythmbox] add grilo plugin
- Date: Sat, 10 Sep 2011 13:38:09 +0000 (UTC)
commit 0296f0fe21708cba3b2402dc99f30a5ff13feac2
Author: Jonathan Matthew <jonathan d14n org>
Date: Sat Sep 10 23:18:57 2011 +1000
add grilo plugin
Still a few rough edges, but everything seems to work. This
replaces the coherence and jamendo plugins.
configure.ac | 23 +
data/org.gnome.rhythmbox.gschema.xml | 4 +
plugins/Makefile.am | 4 +
plugins/grilo/Makefile.am | 45 ++
plugins/grilo/grilo.plugin.in | 8 +
plugins/grilo/rb-grilo-plugin.c | 184 ++++++
plugins/grilo/rb-grilo-source.c | 1104 ++++++++++++++++++++++++++++++++++
plugins/grilo/rb-grilo-source.h | 65 ++
po/POTFILES.in | 3 +
9 files changed, 1440 insertions(+), 0 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 98776f2..59264ee 100644
--- a/configure.ac
+++ b/configure.ac
@@ -58,6 +58,7 @@ LIBSOUP_REQS=2.26.0
GUDEV_REQS=143
LIBMTP_REQS=0.3.0
LIBPEAS_REQS=0.7.3
+GRILO_REQS=0.1.17
LIBNOTIFY_REQS=0.7.0
BRASERO_MIN_REQS=2.31.5
@@ -784,6 +785,27 @@ AC_SUBST(JSON_GLIB_CFLAGS)
AC_SUBST(JSON_GLIB_LIBS)
dnl ================================================================
+dnl grilo plugin
+dnl ================================================================
+AC_ARG_ENABLE(grilo,
+ AC_HELP_STRING([--disable-grilo],
+ [Disable Grilo support]),,
+ enable_grilo=auto)
+if test "x$enable_grilo" != "xno"; then
+ PKG_CHECK_MODULES(GRILO, grilo-0.1 >= $GRILO_REQS, have_grilo=yes, have_grilo=no)
+ if test "x$have_grilo" = "xno" -a "x$enable_grilo" = "xyes"; then
+ AC_MSG_ERROR([Grilo support explicitly requested, but grilo couldn not be found])
+ fi
+ if test "x$have_grilo" = "xyes"; then
+ AC_DEFINE(HAVE_GRILO, 1, [Define if Grilo support is enabled])
+ fi
+fi
+
+AM_CONDITIONAL(ENABLE_GRILO, test x"$have_grilo" = "xyes")
+AC_SUBST(GRILO_CFLAGS)
+AC_SUBST(GRILO_LIBS)
+
+dnl ================================================================
dnl end-game
dnl ================================================================
@@ -861,6 +883,7 @@ plugins/dbus-media-server/Makefile
plugins/rbzeitgeist/Makefile
plugins/notification/Makefile
plugins/visualizer/Makefile
+plugins/grilo/Makefile
bindings/Makefile
bindings/vala/Makefile
bindings/gi/Makefile
diff --git a/data/org.gnome.rhythmbox.gschema.xml b/data/org.gnome.rhythmbox.gschema.xml
index 4201257..f8986f4 100644
--- a/data/org.gnome.rhythmbox.gschema.xml
+++ b/data/org.gnome.rhythmbox.gschema.xml
@@ -415,4 +415,8 @@
<description>The frame rate and size to use for visual effects</description>
</key>
</schema>
+
+ <schema id="org.gnome.rhythmbox.plugins.grilo" path="/org/gnome/rhythmbox/plugins/grilo/">
+ <child name="source" schema="org.gnome.rhythmbox.source"/>
+ </schema>
</schemalist>
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 50c9b4f..faa09d7 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -66,3 +66,7 @@ endif
if USE_NOTIFY
SUBDIRS += notification
endif
+
+if ENABLE_GRILO
+SUBDIRS += grilo
+endif
diff --git a/plugins/grilo/Makefile.am b/plugins/grilo/Makefile.am
new file mode 100644
index 0000000..cab8672
--- /dev/null
+++ b/plugins/grilo/Makefile.am
@@ -0,0 +1,45 @@
+plugindir = $(PLUGINDIR)/grilo
+plugindatadir = $(PLUGINDATADIR)/grilo
+plugin_LTLIBRARIES = libgrilo.la
+
+libgrilo_la_SOURCES = \
+ rb-grilo-plugin.c \
+ rb-grilo-source.c \
+ rb-grilo-source.h
+
+libgrilo_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
+libgrilo_la_LIBTOOLFLAGS = --tag=disable-static
+
+libgrilo_la_LIBADD = \
+ $(top_builddir)/shell/librhythmbox-core.la \
+ $(GRILO_LIBS)
+
+INCLUDES = \
+ -DGNOMELOCALEDIR=\""$(datadir)/locale"\" \
+ -DG_LOG_DOMAIN=\"Rhythmbox\" \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/lib \
+ -I$(top_srcdir)/rhythmdb \
+ -I$(top_srcdir)/widgets \
+ -I$(top_srcdir)/sources \
+ -I$(top_srcdir)/plugins \
+ -I$(top_srcdir)/shell \
+ -DPIXMAP_DIR=\""$(datadir)/pixmaps"\" \
+ -DSHARE_DIR=\"$(pkgdatadir)\" \
+ -DDATADIR=\""$(datadir)"\" \
+ $(RHYTHMBOX_CFLAGS) \
+ $(GRILO_CFLAGS) \
+ -D_XOPEN_SOURCE -D_BSD_SOURCE
+
+libgrilo_la_LIBADD += $(NULL)
+
+plugin_in_files = grilo.plugin.in
+
+%.plugin: %.plugin.in $(INTLTOOL_MERGE) $(wildcard $(top_srcdir)/po/*po) ; $(INTLTOOL_MERGE) $(top_srcdir)/po $< $@ -d -u -c $(top_builddir)/po/.intltool-merge-cache
+
+plugin_DATA = $(plugin_in_files:.plugin.in=.plugin)
+
+EXTRA_DIST = $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
+DISTCLEANFILES = $(plugin_DATA)
diff --git a/plugins/grilo/grilo.plugin.in b/plugins/grilo/grilo.plugin.in
new file mode 100644
index 0000000..38df8dd
--- /dev/null
+++ b/plugins/grilo/grilo.plugin.in
@@ -0,0 +1,8 @@
+[Plugin]
+Module=grilo
+IAge=2
+_Name=Grilo media browser
+_Description=Browse various local and Internet media sources
+Authors=Jonathan Matthew
+Copyright=Copyright  2011 Jonathan Matthew
+Website=http://www.rhythmbox.org/
diff --git a/plugins/grilo/rb-grilo-plugin.c b/plugins/grilo/rb-grilo-plugin.c
new file mode 100644
index 0000000..07e6ef0
--- /dev/null
+++ b/plugins/grilo/rb-grilo-plugin.c
@@ -0,0 +1,184 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Jonathan Matthew
+ *
+ * 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, 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 <glib.h>
+#include <glib-object.h>
+#include <glib/gi18n-lib.h>
+#include <gmodule.h>
+#include <gtk/gtk.h>
+
+#include <grilo.h>
+
+#include "rb-plugin-macros.h"
+#include "rb-debug.h"
+#include "rb-shell.h"
+#include "rb-grilo-source.h"
+#include "rb-display-page-group.h"
+
+#define RB_TYPE_GRILO_PLUGIN (rb_grilo_plugin_get_type ())
+#define RB_GRILO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_GRILO_PLUGIN, RBGriloPlugin))
+#define RB_GRILO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_GRILO_PLUGIN, RBGriloPluginClass))
+#define RB_IS_GRILO_PLUGIN(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_GRILO_PLUGIN))
+#define RB_IS_GRILO_PLUGIN_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_GRILO_PLUGIN))
+#define RB_GRILO_PLUGIN_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_GRILO_PLUGIN, RBGriloPluginClass))
+
+static const char *ignored_plugins[] = {
+ "grl-apple-trailers",
+ "grl-bookmarks",
+ "grl-filesystem",
+ "grl-flickr",
+ "grl-podcasts",
+ "grl-tracker",
+ "grl-vimeo",
+ "grl-youtube"
+};
+
+typedef struct
+{
+ PeasExtensionBase parent;
+
+ GrlPluginRegistry *registry;
+ GHashTable *sources;
+} RBGriloPlugin;
+
+typedef struct
+{
+ PeasExtensionBaseClass parent_class;
+} RBGriloPluginClass;
+
+G_MODULE_EXPORT void peas_register_types (PeasObjectModule *module);
+GType rb_grilo_plugin_get_type (void) G_GNUC_CONST;
+
+static void rb_grilo_plugin_init (RBGriloPlugin *plugin);
+
+RB_DEFINE_PLUGIN(RB_TYPE_GRILO_PLUGIN, RBGriloPlugin, rb_grilo_plugin,)
+
+static void
+rb_grilo_plugin_init (RBGriloPlugin *plugin)
+{
+ rb_debug ("RBGriloPlugin initialising");
+}
+
+static void
+rb_grilo_plugin_source_deleted (RBGriloSource *source, RBGriloPlugin *plugin)
+{
+ GrlMediaSource *media_source;
+
+ g_object_get (source, "media-source", &media_source, NULL);
+ g_hash_table_remove (plugin->sources, media_source);
+ g_object_unref (media_source);
+}
+
+static void
+grilo_source_added_cb (GrlPluginRegistry *registry, GrlMediaPlugin *grilo_plugin, RBGriloPlugin *plugin)
+{
+ RBSource *grilo_source;
+ RBShell *shell;
+ int i;
+
+ if (GRL_IS_MEDIA_SOURCE (grilo_plugin) == FALSE) {
+ /* TODO use metadata sources for album art and lyrics */
+ rb_debug ("grilo source %s is not interesting",
+ grl_media_plugin_get_name (grilo_plugin));
+ return;
+ }
+
+ for (i = 0; i < G_N_ELEMENTS (ignored_plugins); i++) {
+ if (g_str_equal (ignored_plugins[i], grl_media_plugin_get_id (grilo_plugin))) {
+ rb_debug ("grilo source %s is blacklisted",
+ grl_media_plugin_get_name (grilo_plugin));
+ return;
+ }
+ }
+
+ rb_debug ("new grilo source: %s", grl_media_plugin_get_name (grilo_plugin));
+
+ grilo_source = rb_grilo_source_new (G_OBJECT (plugin), GRL_MEDIA_SOURCE (grilo_plugin));
+ g_hash_table_insert (plugin->sources, grilo_plugin, grilo_source);
+
+ /* probably put some sources under 'shared', some under 'stores'? */
+ g_object_get (plugin, "object", &shell, NULL);
+ rb_shell_append_display_page (shell, RB_DISPLAY_PAGE (grilo_source), RB_DISPLAY_PAGE_GROUP_SHARED);
+ g_object_unref (shell);
+}
+
+static void
+impl_activate (PeasActivatable *plugin)
+{
+ RBGriloPlugin *pi = RB_GRILO_PLUGIN (plugin);
+ GError *error = NULL;
+
+ pi->sources = g_hash_table_new_full (g_direct_hash,
+ g_direct_equal,
+ g_object_unref,
+ g_object_unref);
+
+ grl_init (0, NULL);
+ pi->registry = grl_plugin_registry_get_default ();
+ g_signal_connect (pi->registry, "source-added", G_CALLBACK (grilo_source_added_cb), pi);
+ if (grl_plugin_registry_load_all (pi->registry, &error) == FALSE) {
+ g_warning ("Failed to load Grilo plugins: %s", error->message);
+ g_clear_error (&error);
+ }
+}
+
+static void
+_delete_cb (GVolume *volume,
+ RBSource *source,
+ RBGriloPlugin *plugin)
+{
+ /* block the source deleted handler so we don't modify the hash table
+ * while iterating it.
+ */
+ g_signal_handlers_block_by_func (source, rb_grilo_plugin_source_deleted, plugin);
+ rb_display_page_delete_thyself (RB_DISPLAY_PAGE (source));
+}
+
+static void
+impl_deactivate (PeasActivatable *bplugin)
+{
+ RBGriloPlugin *plugin = RB_GRILO_PLUGIN (bplugin);
+
+ g_hash_table_foreach (plugin->sources, (GHFunc)_delete_cb, plugin);
+ g_hash_table_destroy (plugin->sources);
+ plugin->sources = NULL;
+
+ g_object_unref (plugin->registry);
+ plugin->registry = NULL;
+}
+
+G_MODULE_EXPORT void
+peas_register_types (PeasObjectModule *module)
+{
+ rb_grilo_plugin_register_type (G_TYPE_MODULE (module));
+ _rb_grilo_source_register_type (G_TYPE_MODULE (module));
+ peas_object_module_register_extension_type (module,
+ PEAS_TYPE_ACTIVATABLE,
+ RB_TYPE_GRILO_PLUGIN);
+}
diff --git a/plugins/grilo/rb-grilo-source.c b/plugins/grilo/rb-grilo-source.c
new file mode 100644
index 0000000..88e22c4
--- /dev/null
+++ b/plugins/grilo/rb-grilo-source.c
@@ -0,0 +1,1104 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2011 Jonathan Matthew
+ *
+ * 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 <string.h>
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <grilo.h>
+
+#include "rhythmdb.h"
+#include "rb-shell.h"
+#include "rb-shell-player.h"
+#include "rb-grilo-source.h"
+#include "rb-util.h"
+#include "rb-debug.h"
+#include "rb-file-helpers.h"
+#include "rb-gst-media-types.h"
+#include "rb-search-entry.h"
+
+/* number of items to check before giving up on finding any
+ * of a particular type
+ */
+#define CONTAINER_GIVE_UP_POINT 100
+
+/* maximum number of tracks to fetch before stopping and
+ * requiring the user to ask for more.
+ */
+#define CONTAINER_MAX_TRACKS 1000
+
+/* number of items to fetch at once */
+#define CONTAINER_FETCH_SIZE 50
+
+enum {
+ CONTAINER_UNKNOWN_MEDIA = 0,
+ CONTAINER_NO_MEDIA,
+ CONTAINER_HAS_MEDIA
+};
+
+enum
+{
+ PROP_0,
+ PROP_GRILO_SOURCE
+};
+
+static void rb_grilo_source_dispose (GObject *object);
+static void rb_grilo_source_finalize (GObject *object);
+static void rb_grilo_source_constructed (GObject *object);
+static void impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec);
+static void impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec);
+
+static void browser_selection_changed_cb (GtkTreeSelection *selection, RBGriloSource *source);
+static void browser_row_expanded_cb (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, RBGriloSource *source);
+static void scroll_adjust_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source);
+static void scroll_adjust_value_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source);
+static gboolean maybe_expand_container (RBGriloSource *source);
+static void fetch_more_cb (GtkInfoBar *bar, gint response, RBGriloSource *source);
+static void search_cb (RBSearchEntry *search, const char *text, RBGriloSource *source);
+static void notify_sort_order_cb (GObject *object, GParamSpec *pspec, RBGriloSource *source);
+
+static void impl_delete_thyself (RBDisplayPage *page);
+static void impl_selected (RBDisplayPage *page);
+
+static RBEntryView *impl_get_entry_view (RBSource *source);
+
+typedef struct
+{
+ GrlData *grilo_data;
+ GrlData *grilo_container;
+} RBGriloEntryData;
+
+struct _RBGriloSourcePrivate
+{
+ GrlMediaSource *grilo_source;
+ GList *grilo_keys;
+
+ RhythmDBEntryType *entry_type;
+
+ /* some widgets and things */
+ GtkWidget *paned;
+ RhythmDBQueryModel *query_model;
+ RBEntryView *entry_view;
+ GtkTreeStore *browser_model;
+ GtkWidget *browser_view;
+ gboolean done_initial_browse;
+ GtkWidget *info_bar;
+ GtkWidget *info_bar_label;
+ RBSearchEntry *search_entry;
+
+ /* current browsing operation (should allow multiple concurrent ops?) */
+ guint browse_op;
+ GrlMedia *browse_container;
+ GtkTreeIter browse_container_iter;
+ guint browse_position;
+ gboolean browse_got_results;
+ gboolean browse_got_media;
+ guint maybe_expand_idle;
+
+ /* current media browse operation */
+ guint media_browse_op;
+ char *search_text;
+ GrlMedia *media_browse_container;
+ GtkTreeIter media_browse_container_iter;
+ guint media_browse_position;
+ gboolean media_browse_got_results;
+ gboolean media_browse_got_containers;
+ guint media_browse_limit;
+
+ RhythmDB *db;
+};
+
+G_DEFINE_DYNAMIC_TYPE (RBGriloSource, rb_grilo_source, RB_TYPE_SOURCE)
+
+/* entry type */
+typedef struct _RhythmDBEntryType RBGriloEntryType;
+typedef struct _RhythmDBEntryTypeClass RBGriloEntryTypeClass;
+
+GType rb_grilo_entry_type_get_type (void);
+
+G_DEFINE_DYNAMIC_TYPE (RBGriloEntryType, rb_grilo_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
+
+static void
+rb_grilo_entry_type_destroy_entry (RhythmDBEntryType *etype, RhythmDBEntry *entry)
+{
+ RBGriloEntryData *data;
+
+ data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBGriloEntryData);
+ g_object_unref (data->grilo_data);
+ if (data->grilo_container != NULL)
+ g_object_unref (data->grilo_container);
+}
+
+static void
+rb_grilo_entry_type_class_init (RBGriloEntryTypeClass *klass)
+{
+ RhythmDBEntryTypeClass *etype_class = RHYTHMDB_ENTRY_TYPE_CLASS (klass);
+ etype_class->can_sync_metadata = (RhythmDBEntryTypeBooleanFunc) rb_true_function;
+ etype_class->sync_metadata = (RhythmDBEntryTypeSyncFunc) rb_null_function;
+ etype_class->destroy_entry = rb_grilo_entry_type_destroy_entry;
+}
+
+static void
+rb_grilo_entry_type_class_finalize (RBGriloEntryTypeClass *klass)
+{
+}
+
+static void
+rb_grilo_entry_type_init (RBGriloEntryType *etype)
+{
+}
+
+static void
+rb_grilo_source_class_init (RBGriloSourceClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ RBDisplayPageClass *page_class = RB_DISPLAY_PAGE_CLASS (klass);
+ RBSourceClass *source_class = RB_SOURCE_CLASS (klass);
+
+ object_class->constructed = rb_grilo_source_constructed;
+ object_class->dispose = rb_grilo_source_dispose;
+ object_class->finalize = rb_grilo_source_finalize;
+ object_class->set_property = impl_set_property;
+ object_class->get_property = impl_get_property;
+
+ page_class->delete_thyself = impl_delete_thyself;
+ page_class->selected = impl_selected;
+
+ source_class->impl_get_entry_view = impl_get_entry_view;
+
+ g_object_class_install_property (object_class,
+ PROP_GRILO_SOURCE,
+ g_param_spec_object ("grilo-source",
+ "grilo source",
+ "grilo source object",
+ GRL_TYPE_MEDIA_SOURCE,
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
+
+ g_type_class_add_private (klass, sizeof (RBGriloSourcePrivate));
+}
+
+static void
+rb_grilo_source_class_finalize (RBGriloSourceClass *klass)
+{
+}
+
+static void
+rb_grilo_source_init (RBGriloSource *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, RB_TYPE_GRILO_SOURCE, RBGriloSourcePrivate);
+}
+
+static void
+rb_grilo_source_finalize (GObject *object)
+{
+ RBGriloSource *source = RB_GRILO_SOURCE (object);
+
+ g_free (source->priv->search_text);
+
+ g_list_free (source->priv->grilo_keys);
+
+ G_OBJECT_CLASS (rb_grilo_source_parent_class)->finalize (object);
+}
+
+static void
+rb_grilo_source_dispose (GObject *object)
+{
+ RBGriloSource *source = RB_GRILO_SOURCE (object);
+
+ if (source->priv->browse_op != 0) {
+ grl_operation_cancel (source->priv->browse_op);
+ source->priv->browse_op = 0;
+ }
+
+ if (source->priv->media_browse_op != 0) {
+ grl_operation_cancel (source->priv->media_browse_op);
+ source->priv->media_browse_op = 0;
+ }
+
+ if (source->priv->query_model != NULL) {
+ g_object_unref (source->priv->query_model);
+ source->priv->query_model = NULL;
+ }
+
+ if (source->priv->entry_type != NULL) {
+ g_object_unref (source->priv->entry_type);
+ source->priv->entry_type = NULL;
+ }
+
+ if (source->priv->maybe_expand_idle != 0) {
+ g_source_remove (source->priv->maybe_expand_idle);
+ source->priv->maybe_expand_idle = 0;
+ }
+
+ G_OBJECT_CLASS (rb_grilo_source_parent_class)->dispose (object);
+}
+
+static void
+rb_grilo_source_constructed (GObject *object)
+{
+ RBGriloSource *source;
+ RBShell *shell;
+ RBShellPlayer *shell_player;
+ const GList *source_keys;
+ GtkTreeViewColumn *column;
+ GtkCellRenderer *renderer;
+ GtkTreeSelection *selection;
+ GtkWidget *scrolled;
+ GtkWidget *vbox;
+ GtkWidget *mainbox;
+ GtkAdjustment *adjustment;
+
+ RB_CHAIN_GOBJECT_METHOD (rb_grilo_source_parent_class, constructed, object);
+ source = RB_GRILO_SOURCE (object);
+
+ g_object_get (source, "shell", &shell, NULL);
+ g_object_get (shell,
+ "db", &source->priv->db,
+ "shell-player", &shell_player,
+ NULL);
+ g_object_unref (shell);
+
+ g_object_get (source, "entry-type", &source->priv->entry_type, NULL);
+
+ source->priv->entry_view = rb_entry_view_new (source->priv->db, G_OBJECT (shell_player), TRUE, FALSE);
+ g_object_unref (shell_player);
+ g_signal_connect (source->priv->entry_view,
+ "notify::sort-order",
+ G_CALLBACK (notify_sort_order_cb),
+ source);
+
+ source_keys = grl_metadata_source_supported_keys (GRL_METADATA_SOURCE (source->priv->grilo_source));
+
+ if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_TRACK_NUMBER)) {
+ rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_TRACK_NUMBER, FALSE);
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
+ GRL_METADATA_KEY_TRACK_NUMBER);
+ }
+
+ if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_TITLE)) {
+ rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_TITLE, TRUE);
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
+ GRL_METADATA_KEY_TITLE);
+ }
+
+ if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_GENRE)) {
+ rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_GENRE, FALSE);
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
+ GRL_METADATA_KEY_GENRE);
+ }
+ if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_ARTIST)) {
+ rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_ARTIST, FALSE);
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
+ GRL_METADATA_KEY_ARTIST);
+ }
+ if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_ALBUM)) {
+ rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_ALBUM, FALSE);
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
+ GRL_METADATA_KEY_ALBUM);
+ }
+ /*
+ if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_DATE)) {
+ rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_YEAR, FALSE);
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
+ GRL_METADATA_KEY_DATE);
+ }
+ */
+ if (g_list_find ((GList *)source_keys, GRL_METADATA_KEY_DURATION)) {
+ rb_entry_view_append_column (source->priv->entry_view, RB_ENTRY_VIEW_COL_DURATION, FALSE);
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys,
+ GRL_METADATA_KEY_DURATION);
+ }
+
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys, GRL_METADATA_KEY_CHILDCOUNT);
+ source->priv->grilo_keys = g_list_prepend (source->priv->grilo_keys, GRL_METADATA_KEY_URL);
+
+ /* probably add an image column too? */
+ source->priv->browser_model = gtk_tree_store_new (4, GRL_TYPE_MEDIA, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
+ source->priv->browser_view = gtk_tree_view_new ();
+ gtk_tree_view_set_model (GTK_TREE_VIEW (source->priv->browser_view), GTK_TREE_MODEL (source->priv->browser_model));
+
+ column = gtk_tree_view_column_new ();
+ renderer = gtk_cell_renderer_text_new ();
+ gtk_tree_view_column_set_title (column, _("Browse"));
+ gtk_tree_view_column_pack_start (column, renderer, FALSE);
+ gtk_tree_view_column_add_attribute (column, renderer, "text", 1);
+ gtk_tree_view_column_set_expand (column, TRUE);
+ gtk_tree_view_column_set_sizing (column, GTK_TREE_VIEW_COLUMN_FIXED);
+
+ gtk_tree_view_append_column (GTK_TREE_VIEW (source->priv->browser_view), column);
+ gtk_tree_view_set_show_expanders (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
+ gtk_tree_view_set_expander_column (GTK_TREE_VIEW (source->priv->browser_view), column);
+ gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
+ gtk_tree_view_set_fixed_height_mode (GTK_TREE_VIEW (source->priv->browser_view), TRUE);
+
+ g_signal_connect (source->priv->browser_view, "row-expanded", G_CALLBACK (browser_row_expanded_cb), source);
+ selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (source->priv->browser_view));
+ gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE); /* should be multiple eventually */
+ g_signal_connect (selection, "changed", G_CALLBACK (browser_selection_changed_cb), source);
+
+ scrolled = gtk_scrolled_window_new (NULL, NULL);
+ gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_IN);
+ adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (scrolled));
+ g_signal_connect (adjustment, "changed", G_CALLBACK (scroll_adjust_changed_cb), source);
+ g_signal_connect (adjustment, "value-changed", G_CALLBACK (scroll_adjust_value_changed_cb), source);
+ gtk_container_add (GTK_CONTAINER (scrolled), source->priv->browser_view);
+
+ mainbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (source), mainbox, TRUE, TRUE, 0);
+
+ /* search bar (if the source supports searching) */
+ if (grl_metadata_source_supported_operations (GRL_METADATA_SOURCE (source->priv->grilo_source)) & GRL_OP_SEARCH) {
+ GtkWidget *align;
+
+ align = gtk_alignment_new (1.0, 0.5, 0.5, 1.0);
+ source->priv->search_entry = rb_search_entry_new ();
+ g_object_set (source->priv->search_entry, "explicit-mode", TRUE, NULL);
+ g_signal_connect (source->priv->search_entry, "search", G_CALLBACK (search_cb), source);
+ g_signal_connect (source->priv->search_entry, "activate", G_CALLBACK (search_cb), source);
+ gtk_container_add (GTK_CONTAINER (align), GTK_WIDGET (source->priv->search_entry));
+ gtk_box_pack_start (GTK_BOX (mainbox), align, FALSE, FALSE, 0);
+ }
+
+ /* info bar */
+ source->priv->info_bar_label = gtk_label_new ("");
+ source->priv->info_bar = gtk_info_bar_new ();
+ gtk_info_bar_set_message_type (GTK_INFO_BAR (source->priv->info_bar), GTK_MESSAGE_INFO);
+ gtk_info_bar_add_button (GTK_INFO_BAR (source->priv->info_bar), _("Fetch more tracks"), GTK_RESPONSE_OK);
+ gtk_container_add (GTK_CONTAINER (gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->info_bar))),
+ source->priv->info_bar_label);
+ gtk_widget_show (GTK_WIDGET (source->priv->info_bar_label));
+ gtk_widget_set_no_show_all (GTK_WIDGET (source->priv->info_bar), TRUE);
+ g_signal_connect (source->priv->info_bar, "response", G_CALLBACK (fetch_more_cb), source);
+
+ /* don't allow the browser to be hidden? */
+ source->priv->paned = gtk_hpaned_new ();
+ rb_source_bind_settings (RB_SOURCE (source), GTK_WIDGET (source->priv->entry_view), source->priv->paned, NULL);
+ gtk_paned_pack1 (GTK_PANED (source->priv->paned), scrolled, FALSE, FALSE);
+
+ vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6);
+ gtk_box_pack_start (GTK_BOX (vbox), GTK_WIDGET (source->priv->entry_view), TRUE, TRUE, 0);
+ gtk_box_pack_start (GTK_BOX (vbox), source->priv->info_bar, FALSE, FALSE, 0);
+ gtk_paned_pack2 (GTK_PANED (source->priv->paned), vbox, TRUE, FALSE);
+
+ gtk_box_pack_start (GTK_BOX (mainbox), source->priv->paned, TRUE, TRUE, 0);
+
+ gtk_widget_show_all (GTK_WIDGET (source));
+}
+
+RBSource *
+rb_grilo_source_new (GObject *plugin, GrlMediaSource *grilo_source)
+{
+ GObject *source;
+ RBShell *shell;
+ GSettings *settings;
+ RhythmDBEntryType *entry_type;
+ RhythmDB *db;
+ char *name;
+
+ name = g_strdup_printf ("grilo:%s", grl_media_plugin_get_id (GRL_MEDIA_PLUGIN (grilo_source)));
+
+ g_object_get (plugin, "object", &shell, NULL);
+ g_object_get (shell, "db", &db, NULL);
+ entry_type = g_object_new (rb_grilo_entry_type_get_type (),
+ "db", db,
+ "name", name,
+ "save-to-disk", FALSE,
+ "category", RHYTHMDB_ENTRY_NORMAL,
+ "type-data-size", sizeof(RBGriloEntryData),
+ NULL);
+ rhythmdb_register_entry_type (db, entry_type);
+ g_object_unref (db);
+ g_free (name);
+
+ settings = g_settings_new ("org.gnome.rhythmbox.plugins.grilo");
+ source = g_object_new (RB_TYPE_GRILO_SOURCE,
+ "name", grl_metadata_source_get_name (GRL_METADATA_SOURCE (grilo_source)),
+ "entry-type", entry_type,
+ "shell", shell,
+ "plugin", plugin,
+ "show-browser", FALSE,
+ "settings", g_settings_get_child (settings, "source"),
+ "grilo-source", grilo_source,
+ NULL);
+ g_object_unref (settings);
+
+ rb_shell_register_entry_type_for_source (shell, RB_SOURCE (source), entry_type);
+
+ g_object_unref (shell);
+ return RB_SOURCE (source);
+}
+
+static void
+impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
+{
+ RBGriloSource *source = RB_GRILO_SOURCE (object);
+ switch (prop_id) {
+ case PROP_GRILO_SOURCE:
+ source->priv->grilo_source = g_value_get_object (value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
+{
+ RBGriloSource *source = RB_GRILO_SOURCE (object);
+
+ switch (prop_id) {
+ case PROP_GRILO_SOURCE:
+ g_value_set_object (value, source->priv->grilo_source);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+ break;
+ }
+}
+
+static void
+impl_delete_thyself (RBDisplayPage *page)
+{
+ RBGriloSource *source = RB_GRILO_SOURCE (page);
+ RhythmDBEntryType *entry_type;
+
+ if (source->priv->browse_op != 0) {
+ grl_operation_cancel (source->priv->browse_op);
+ source->priv->browse_op = 0;
+ }
+
+ if (source->priv->media_browse_op != 0) {
+ grl_operation_cancel (source->priv->media_browse_op);
+ source->priv->media_browse_op = 0;
+ }
+
+ g_object_get (source, "entry-type", &entry_type, NULL);
+ rhythmdb_entry_delete_by_type (source->priv->db, entry_type);
+ g_object_unref (entry_type);
+
+ rhythmdb_commit (source->priv->db);
+}
+
+void
+_rb_grilo_source_register_type (GTypeModule *module)
+{
+ rb_grilo_source_register_type (module);
+ rb_grilo_entry_type_register_type (module);
+}
+
+/* grilo media -> rhythmdb entry */
+
+static void
+set_string_prop_from_key (RhythmDB *db, RhythmDBEntry *entry, RhythmDBPropType prop, GrlData *data, GrlKeyID key)
+{
+ GValue v = {0,};
+ if (grl_data_has_key (data, key) == FALSE)
+ return;
+
+ g_value_init (&v, G_TYPE_STRING);
+ g_value_set_string (&v, grl_data_get_string (data, key));
+ rhythmdb_entry_set (db, entry, prop, &v);
+ g_value_unset (&v);
+}
+
+static RhythmDBEntry *
+create_entry_for_media (RhythmDB *db, RhythmDBEntryType *entry_type, GrlData *data, GrlData *container)
+{
+ RhythmDBEntry *entry;
+ RBGriloEntryData *entry_data;
+
+ entry = rhythmdb_entry_lookup_by_location (db, grl_media_get_url (GRL_MEDIA (data)));
+ if (entry != NULL) {
+ return entry;
+ }
+
+ rb_debug ("creating entry for %s / %s", grl_media_get_url (GRL_MEDIA (data)), grl_media_get_id (GRL_MEDIA (data)));
+
+ entry = rhythmdb_entry_new (db, entry_type, grl_media_get_url (GRL_MEDIA (data))); /* just use the url? */
+ if (entry == NULL) {
+ /* crap. */
+ return NULL;
+ }
+
+ set_string_prop_from_key (db, entry, RHYTHMDB_PROP_TITLE, data, GRL_METADATA_KEY_TITLE);
+ set_string_prop_from_key (db, entry, RHYTHMDB_PROP_ALBUM, data, GRL_METADATA_KEY_ALBUM);
+ set_string_prop_from_key (db, entry, RHYTHMDB_PROP_ARTIST, data, GRL_METADATA_KEY_ARTIST);
+ set_string_prop_from_key (db, entry, RHYTHMDB_PROP_GENRE, data, GRL_METADATA_KEY_GENRE);
+ set_string_prop_from_key (db, entry, RHYTHMDB_PROP_TITLE, data, GRL_METADATA_KEY_TITLE);
+
+ if (grl_data_has_key (data, GRL_METADATA_KEY_DATE)) {
+ /* something - grilo has this as a string? */
+ }
+
+ if (grl_data_has_key (data, GRL_METADATA_KEY_BITRATE)) {
+ GValue v = {0,};
+ g_value_init (&v, G_TYPE_ULONG);
+ g_value_set_ulong (&v, grl_data_get_int (data, GRL_METADATA_KEY_BITRATE));
+ rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_BITRATE, &v);
+ g_value_unset (&v);
+ }
+
+ if (grl_data_has_key (data, GRL_METADATA_KEY_DURATION)) {
+ /* this is probably in seconds */
+ GValue v = {0,};
+ g_value_init (&v, G_TYPE_ULONG);
+ g_value_set_ulong (&v, grl_data_get_int (data, GRL_METADATA_KEY_DURATION));
+ rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &v);
+ g_value_unset (&v);
+ }
+
+ if (grl_data_has_key (data, GRL_METADATA_KEY_MIME)) {
+ const char *media_type;
+ media_type = rb_gst_mime_type_to_media_type (grl_data_get_string (data, GRL_METADATA_KEY_MIME));
+ if (media_type) {
+ GValue v = {0,};
+ g_value_init (&v, G_TYPE_STRING);
+ g_value_set_string (&v, media_type);
+ rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MEDIA_TYPE, &v);
+ g_value_unset (&v);
+ }
+ }
+
+ /* rating and play count? */
+
+
+ entry_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBGriloEntryData);
+ entry_data->grilo_data = g_object_ref (data);
+ if (container != NULL) {
+ entry_data->grilo_container = g_object_ref (container);
+ }
+
+ /* might want to consider batching this */
+ rhythmdb_commit (db);
+
+ return entry;
+}
+
+/* container browsing */
+
+static void browse_next (RBGriloSource *source);
+
+static void
+delete_marker_row (RBGriloSource *source, GtkTreeIter *iter)
+{
+ GtkTreeIter marker_iter;
+ if (gtk_tree_model_iter_children (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter, iter)) {
+ do {
+ GrlMedia *container;
+ gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter,
+ 0, &container,
+ -1);
+ if (container == NULL) {
+ gtk_tree_store_remove (GTK_TREE_STORE (source->priv->browser_model), &marker_iter);
+ break;
+ }
+ } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &marker_iter));
+ }
+}
+
+static void
+set_container_type (RBGriloSource *source, GtkTreeIter *iter, gboolean has_media)
+{
+ int container_type;
+
+ gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model),
+ iter,
+ 2, &container_type,
+ -1);
+ if (container_type == CONTAINER_UNKNOWN_MEDIA) {
+ container_type = has_media ? CONTAINER_HAS_MEDIA : CONTAINER_NO_MEDIA;
+ }
+
+ gtk_tree_store_set (source->priv->browser_model,
+ iter,
+ 2, container_type,
+ -1);
+}
+
+static void
+grilo_browse_cb (GrlMediaSource *grilo_source, guint operation_id, GrlMedia *media, guint remaining, RBGriloSource *source, const GError *error)
+{
+ if (operation_id != source->priv->browse_op) {
+ return;
+ }
+
+ if (error != NULL) {
+ /* do something? */
+ rb_debug ("got error for %s: %s", grl_metadata_source_get_name (GRL_METADATA_SOURCE (grilo_source)), error->message);
+ return;
+ }
+
+ if (media != NULL) {
+ source->priv->browse_got_results = TRUE;
+ source->priv->browse_position++;
+ }
+
+ if (media && GRL_IS_MEDIA_BOX (media)) {
+
+ GtkTreeIter new_row;
+ if (source->priv->browse_container == NULL) {
+ /* insert at the end */
+ gtk_tree_store_insert_with_values (source->priv->browser_model,
+ &new_row,
+ NULL,
+ -1,
+ 0, g_object_ref (media),
+ 1, grl_media_get_title (media),
+ 2, CONTAINER_UNKNOWN_MEDIA,
+ 3, 0,
+ -1);
+ } else {
+ int n;
+ /* insert before the expand marker row */
+ n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->browser_model),
+ &source->priv->browse_container_iter);
+ gtk_tree_store_insert_with_values (source->priv->browser_model,
+ &new_row,
+ &source->priv->browse_container_iter,
+ n - 1,
+ 0, g_object_ref (media),
+ 1, grl_media_get_title (media),
+ 2, CONTAINER_UNKNOWN_MEDIA,
+ 3, 0,
+ -1);
+ }
+
+ /* and insert an expand marker below it too */
+ gtk_tree_store_insert_with_values (source->priv->browser_model,
+ NULL,
+ &new_row,
+ -1,
+ 0, NULL,
+ 1, "...", /* needs to be translatable? */
+ 2, CONTAINER_NO_MEDIA,
+ 3, 0,
+ -1);
+ } else if (media && GRL_IS_MEDIA_AUDIO (media)) {
+ source->priv->browse_got_media = TRUE;
+ }
+
+ if (remaining == 0) {
+ source->priv->browse_op = 0;
+ if (source->priv->browse_got_results == FALSE &&
+ source->priv->browse_container != NULL) {
+ /* no more results for this container, so delete the marker row */
+ delete_marker_row (source, &source->priv->browse_container_iter);
+
+ set_container_type (source, &source->priv->browse_container_iter, source->priv->browse_got_media);
+ gtk_tree_store_set (source->priv->browser_model,
+ &source->priv->browse_container_iter,
+ 3, -1,
+ -1);
+ } else if (source->priv->browse_container != NULL) {
+ if (source->priv->browse_position >= CONTAINER_GIVE_UP_POINT &&
+ gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->browser_model),
+ &source->priv->browse_container_iter) == 1) {
+ /* no containers yet, so remove the marker row */
+ delete_marker_row (source, &source->priv->browse_container_iter);
+ } else {
+ /* store browse position for next time we want more */
+ gtk_tree_store_set (source->priv->browser_model,
+ &source->priv->browse_container_iter,
+ 3, source->priv->browse_position,
+ -1);
+ maybe_expand_container (source);
+ }
+
+ } else if (source->priv->browse_got_results && source->priv->browse_container == NULL) {
+ /* get all top-level containers */
+ browse_next (source);
+ }
+ }
+}
+
+static void
+browse_next (RBGriloSource *source)
+{
+ rb_debug ("next browse op for %s (%d)",
+ grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->priv->grilo_source)),
+ source->priv->browse_position);
+ source->priv->browse_got_results = FALSE;
+ source->priv->browse_op = grl_media_source_browse (source->priv->grilo_source,
+ source->priv->browse_container,
+ source->priv->grilo_keys,
+ source->priv->browse_position,
+ CONTAINER_FETCH_SIZE,
+ GRL_RESOLVE_NORMAL,
+ (GrlMediaSourceResultCb) grilo_browse_cb,
+ source);
+}
+
+static void
+start_browse (RBGriloSource *source, GrlMedia *container, GtkTreeIter *container_iter, int position)
+{
+ rb_debug ("starting browse op for %s", grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->priv->grilo_source)));
+
+ /* cancel existing operation? */
+ if (source->priv->browse_op != 0) {
+ grl_operation_cancel (source->priv->browse_op);
+ }
+
+ if (source->priv->browse_container != NULL) {
+ g_object_unref (source->priv->browse_container);
+ }
+ source->priv->browse_container = container;
+ if (container_iter != NULL) {
+ /* hrm, probably have to use row references here.. */
+ source->priv->browse_container_iter = *container_iter;
+ }
+ source->priv->browse_position = position;
+ source->priv->browse_got_media = FALSE;
+
+ browse_next (source);
+}
+
+/* media browsing */
+
+static void media_browse_next (RBGriloSource *source);
+
+static void
+grilo_media_browse_cb (GrlMediaSource *grilo_source, guint operation_id, GrlMedia *media, guint remaining, RBGriloSource *source, const GError *error)
+{
+ if (operation_id != source->priv->media_browse_op) {
+ return;
+ }
+
+ if (error != NULL) {
+ /* do something? */
+ rb_debug ("got error for %s: %s",
+ grl_metadata_source_get_name (GRL_METADATA_SOURCE (grilo_source)),
+ error->message);
+ return;
+ }
+
+ GDK_THREADS_ENTER ();
+ if (media != NULL) {
+ source->priv->media_browse_got_results = TRUE;
+ source->priv->media_browse_position++;
+
+ if (GRL_IS_MEDIA_AUDIO (media)) {
+ RhythmDBEntry *entry;
+ entry = create_entry_for_media (source->priv->db,
+ source->priv->entry_type,
+ GRL_DATA (media),
+ GRL_DATA (source->priv->browse_container));
+ if (entry != NULL) {
+ rhythmdb_query_model_add_entry (source->priv->query_model, entry, -1);
+ }
+ } else if (GRL_IS_MEDIA_BOX (media)) {
+ source->priv->media_browse_got_containers = TRUE;
+ }
+ }
+
+ if (remaining == 0) {
+ source->priv->media_browse_op = 0;
+
+ if (gtk_tree_model_iter_n_children (GTK_TREE_MODEL (source->priv->query_model), NULL) == 0 &&
+ source->priv->media_browse_position >= CONTAINER_GIVE_UP_POINT) {
+ /* if we don't find any media within the first 100 or so results,
+ * assume this container doesn't have any. otherwise we'd load
+ * the entire thing when it got selected, which would suck.
+ */
+ rb_debug ("didn't find any media in %s, giving up", grl_media_get_title (source->priv->media_browse_container));
+ gtk_tree_store_set (source->priv->browser_model,
+ &source->priv->media_browse_container_iter,
+ 2, CONTAINER_NO_MEDIA,
+ -1);
+ } else if (source->priv->media_browse_got_results) {
+ if (source->priv->media_browse_position < source->priv->media_browse_limit) {
+ media_browse_next (source);
+ } else {
+ char *text;
+
+ text = g_strdup_printf (_("Only showing %d results"),
+ source->priv->media_browse_position);
+ gtk_label_set_text (GTK_LABEL (source->priv->info_bar_label), text);
+ g_free (text);
+
+ gtk_widget_show (source->priv->info_bar);
+ }
+ } else if (source->priv->media_browse_got_containers == FALSE &&
+ source->priv->media_browse_container != NULL) {
+ /* make sure there's no marker row for this container */
+ delete_marker_row (source, &source->priv->media_browse_container_iter);
+ }
+ }
+ GDK_THREADS_LEAVE ();
+}
+
+static void
+media_browse_next (RBGriloSource *source)
+{
+ rb_debug ("next media_browse op for %s (%d)",
+ grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->priv->grilo_source)),
+ source->priv->media_browse_position);
+
+ source->priv->media_browse_got_results = FALSE;
+ if (source->priv->media_browse_container != NULL) {
+ source->priv->media_browse_op =
+ grl_media_source_browse (source->priv->grilo_source,
+ source->priv->media_browse_container,
+ source->priv->grilo_keys,
+ source->priv->media_browse_position,
+ CONTAINER_FETCH_SIZE,
+ GRL_RESOLVE_NORMAL,
+ (GrlMediaSourceResultCb) grilo_media_browse_cb,
+ source);
+ } else {
+ source->priv->media_browse_op =
+ grl_media_source_search (source->priv->grilo_source,
+ source->priv->search_text,
+ source->priv->grilo_keys,
+ source->priv->media_browse_position,
+ CONTAINER_FETCH_SIZE,
+ GRL_RESOLVE_NORMAL,
+ (GrlMediaSourceResultCb) grilo_media_browse_cb,
+ source);
+ }
+}
+
+static void
+start_media_browse (RBGriloSource *source, GrlMedia *container, GtkTreeIter *container_iter, guint limit)
+{
+ rb_debug ("starting media browse for %s",
+ grl_metadata_source_get_name (GRL_METADATA_SOURCE (source->priv->grilo_source)));
+
+ /* cancel existing operation? */
+ if (source->priv->media_browse_op != 0) {
+ grl_operation_cancel (source->priv->media_browse_op);
+ }
+
+ if (source->priv->media_browse_container != NULL) {
+ g_object_unref (source->priv->media_browse_container);
+ }
+ source->priv->media_browse_container = container;
+ if (container_iter != NULL) {
+ /* hrm, probably have to use row references here.. */
+ source->priv->media_browse_container_iter = *container_iter;
+ }
+ source->priv->media_browse_position = 0;
+ source->priv->media_browse_limit = limit;
+ source->priv->media_browse_got_containers = FALSE;
+
+ if (source->priv->query_model != NULL) {
+ g_object_unref (source->priv->query_model);
+ }
+ source->priv->query_model = rhythmdb_query_model_new_empty (source->priv->db);
+ rb_entry_view_set_model (RB_ENTRY_VIEW (source->priv->entry_view), source->priv->query_model);
+ g_object_set (source, "query-model", source->priv->query_model, NULL);
+
+ media_browse_next (source);
+}
+
+static void
+fetch_more_cb (GtkInfoBar *bar, gint response, RBGriloSource *source)
+{
+ if (response != GTK_RESPONSE_OK) {
+ return;
+ }
+
+ gtk_widget_hide (GTK_WIDGET (bar));
+ source->priv->media_browse_limit += CONTAINER_MAX_TRACKS;
+ media_browse_next (source);
+}
+
+static gboolean
+expand_from_marker (RBGriloSource *source, GtkTreeIter *iter)
+{
+ /* this is a marker row, fetch more containers underneath the parent */
+ GrlMedia *container;
+ GtkTreeIter browse;
+ int position;
+ gtk_tree_model_iter_parent (GTK_TREE_MODEL (source->priv->browser_model), &browse, iter);
+ gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model),
+ &browse,
+ 0, &container,
+ 3, &position,
+ -1);
+ if (position >= 0) {
+ start_browse (source, container, &browse, position);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void
+browser_selection_changed_cb (GtkTreeSelection *selection, RBGriloSource *source)
+{
+ GtkTreeIter iter;
+ GrlMedia *container;
+ int container_type;
+
+ gtk_widget_hide (GTK_WIDGET (source->priv->info_bar));
+ if (gtk_tree_selection_get_selected (selection, NULL, &iter) == FALSE) {
+ rb_debug ("nothing selected");
+ return;
+ }
+
+ rb_search_entry_clear (source->priv->search_entry);
+
+ gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &iter,
+ 0, &container,
+ 2, &container_type,
+ -1);
+
+ if (container == NULL) {
+ expand_from_marker (source, &iter);
+ } else if (container_type != CONTAINER_NO_MEDIA) {
+ /* fetch media directly under this container */
+ start_media_browse (source, container, &iter, CONTAINER_MAX_TRACKS);
+ } else {
+ /* clear the track list? */
+ }
+}
+
+static gboolean
+maybe_expand_container (RBGriloSource *source)
+{
+ GtkTreePath *path;
+ GtkTreePath *end;
+ GtkTreeIter iter;
+ GtkTreeIter end_iter;
+ GtkTreeIter next;
+ GrlMedia *container;
+ gboolean last;
+
+ source->priv->maybe_expand_idle = 0;
+
+ if (source->priv->browse_op != 0) {
+ rb_debug ("not expanding, already browsing");
+ return FALSE;
+ }
+
+ /* if we find a visible marker row, find more results */
+ if (gtk_tree_view_get_visible_range (GTK_TREE_VIEW (source->priv->browser_view), &path, &end) == FALSE) {
+ rb_debug ("not expanding, nothing to expand");
+ return FALSE;
+ }
+
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (source->priv->browser_model), &iter, path);
+ gtk_tree_model_get_iter (GTK_TREE_MODEL (source->priv->browser_model), &end_iter, end);
+
+ do {
+ gtk_tree_path_free (path);
+ path = gtk_tree_model_get_path (GTK_TREE_MODEL (source->priv->browser_model), &iter);
+ last = (gtk_tree_path_compare (path, end) >= 0);
+ gtk_tree_model_get (GTK_TREE_MODEL (source->priv->browser_model), &iter,
+ 0, &container,
+ -1);
+ if (container == NULL) {
+ if (expand_from_marker (source, &iter)) {
+ rb_debug ("expanding");
+ break;
+ }
+ }
+
+ next = iter;
+ if (gtk_tree_view_row_expanded (GTK_TREE_VIEW (source->priv->browser_view), path) &&
+ gtk_tree_model_iter_has_child (GTK_TREE_MODEL (source->priv->browser_model), &iter)) {
+ gtk_tree_model_iter_children (GTK_TREE_MODEL (source->priv->browser_model), &iter, &next);
+ } else if (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &next)) {
+ iter = next;
+ } else {
+ if (gtk_tree_model_iter_parent (GTK_TREE_MODEL (source->priv->browser_model), &next, &iter) == FALSE) {
+ break;
+ }
+ iter = next;
+ if (gtk_tree_model_iter_next (GTK_TREE_MODEL (source->priv->browser_model), &iter) == FALSE) {
+ break;
+ }
+ }
+ } while (last == FALSE);
+
+ gtk_tree_path_free (path);
+ gtk_tree_path_free (end);
+ return FALSE;
+}
+
+static void
+maybe_expand_container_idle (RBGriloSource *source)
+{
+ if (source->priv->maybe_expand_idle == 0) {
+ source->priv->maybe_expand_idle = g_idle_add ((GSourceFunc)maybe_expand_container, source);
+ }
+}
+
+static void
+browser_row_expanded_cb (GtkTreeView *tree_view, GtkTreeIter *iter, GtkTreePath *path, RBGriloSource *source)
+{
+ maybe_expand_container_idle (source);
+}
+
+static void
+scroll_adjust_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source)
+{
+ maybe_expand_container_idle (source);
+}
+
+static void
+scroll_adjust_value_changed_cb (GtkAdjustment *adjustment, RBGriloSource *source)
+{
+ maybe_expand_container_idle (source);
+}
+
+static void
+impl_selected (RBDisplayPage *page)
+{
+ RBGriloSource *source = RB_GRILO_SOURCE (page);
+ if (source->priv->done_initial_browse == FALSE) {
+ source->priv->done_initial_browse = TRUE;
+ start_browse (source, NULL, NULL, 0);
+ }
+}
+
+static RBEntryView *
+impl_get_entry_view (RBSource *bsource)
+{
+ RBGriloSource *source = RB_GRILO_SOURCE (bsource);
+ return source->priv->entry_view;
+}
+
+static void
+search_cb (RBSearchEntry *search, const char *text, RBGriloSource *source)
+{
+ g_free (source->priv->search_text);
+ source->priv->search_text = g_strdup (text);
+
+ gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (GTK_TREE_VIEW (source->priv->browser_view)));
+
+ start_media_browse (source, NULL, NULL, CONTAINER_MAX_TRACKS);
+}
+
+static void
+notify_sort_order_cb (GObject *object, GParamSpec *pspec, RBGriloSource *source)
+{
+ rb_entry_view_resort_model (RB_ENTRY_VIEW (object));
+}
diff --git a/plugins/grilo/rb-grilo-source.h b/plugins/grilo/rb-grilo-source.h
new file mode 100644
index 0000000..78ddf07
--- /dev/null
+++ b/plugins/grilo/rb-grilo-source.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2011 Jonathan Matthew
+ *
+ * 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_GRILO_SOURCE_H
+#define __RB_GRILO_SOURCE_H
+
+#include <grilo.h>
+
+#include "rhythmdb.h"
+#include "rb-source.h"
+
+G_BEGIN_DECLS
+
+#define RB_TYPE_GRILO_SOURCE (rb_grilo_source_get_type ())
+#define RB_GRILO_SOURCE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), RB_TYPE_GRILO_SOURCE, RBGriloSource))
+#define RB_GRILO_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), RB_TYPE_GRILO_SOURCE, RBGriloSourceClass))
+#define RB_IS_GRILO_SOURCE(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), RB_TYPE_GRILO_SOURCE))
+#define RB_IS_GRILO_SOURCE_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), RB_TYPE_GRILO_SOURCE))
+#define RB_GRILO_SOURCE_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), RB_TYPE_GRILO_SOURCE, RBGriloSourceClass))
+
+typedef struct _RBGriloSourcePrivate RBGriloSourcePrivate;
+
+typedef struct
+{
+ RBSource parent;
+ RBGriloSourcePrivate *priv;
+} RBGriloSource;
+
+typedef struct
+{
+ RBSourceClass parent;
+} RBGriloSourceClass;
+
+RBSource * rb_grilo_source_new (GObject *plugin,
+ GrlMediaSource *grilo_source);
+GType rb_grilo_source_get_type (void);
+void _rb_grilo_source_register_type (GTypeModule *module);
+
+G_END_DECLS
+
+#endif /* __RB_GRILO_SOURCE_H */
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ba7a9f4..6950795 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -80,6 +80,9 @@ plugins/generic-player/rb-generic-player-plugin.c
plugins/generic-player/rb-generic-player-source.c
plugins/generic-player/rb-nokia770-source.c
plugins/generic-player/rb-psp-source.c
+[type: gettext/ini]plugins/grilo/grilo.plugin.in
+plugins/grilo/rb-grilo-plugin.c
+plugins/grilo/rb-grilo-source.c
[type: gettext/ini]plugins/im-status/im-status.plugin.in
[type: gettext/ini]plugins/ipod/ipod.plugin.in
[type: gettext/glade]plugins/ipod/ipod-info.ui
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]