[rhythmbox] audiocd: replace sj-metadata code



commit 19528b5a05f0f89c04b060130133f6242fb24bbb
Author: Jonathan Matthew <jonathan d14n org>
Date:   Mon Sep 3 21:24:04 2012 +1000

    audiocd: replace sj-metadata code
    
    Rather than using libmusicbrainz, we now read disc information
    using GStreamer and talk to the musicbrainz web service using
    libsoup.
    
    We now use a combo box in the info bar rather than a separate dialog
    for choosing between multiple album matches.

 .gitignore                                 |    2 +
 configure.ac                               |   50 --
 doc/reference/Makefile.am                  |    8 -
 plugins/audiocd/Makefile.am                |   89 +--
 plugins/audiocd/multiple-album.ui          |  119 ----
 plugins/audiocd/rb-audiocd-info.c          |  281 ++++++++
 plugins/audiocd/rb-audiocd-info.h          |   68 ++
 plugins/audiocd/rb-audiocd-source.c        | 1044 ++++++++++++++--------------
 plugins/audiocd/rb-musicbrainz-lookup.c    |  524 ++++++++++++++
 plugins/audiocd/rb-musicbrainz-lookup.h    |  110 +++
 plugins/audiocd/sj-error.c                 |   36 -
 plugins/audiocd/sj-error.h                 |   41 --
 plugins/audiocd/sj-metadata-getter.c       |  261 -------
 plugins/audiocd/sj-metadata-getter.h       |   62 --
 plugins/audiocd/sj-metadata-gvfs.c         |  264 -------
 plugins/audiocd/sj-metadata-gvfs.h         |   57 --
 plugins/audiocd/sj-metadata-marshal.list   |    1 -
 plugins/audiocd/sj-metadata-musicbrainz3.c |  460 ------------
 plugins/audiocd/sj-metadata-musicbrainz3.h |   56 --
 plugins/audiocd/sj-metadata-musicbrainz4.c |  615 ----------------
 plugins/audiocd/sj-metadata-musicbrainz4.h |   56 --
 plugins/audiocd/sj-metadata.c              |  225 ------
 plugins/audiocd/sj-metadata.h              |   59 --
 plugins/audiocd/sj-structures.c            |   81 ---
 plugins/audiocd/sj-structures.h            |   99 ---
 plugins/audiocd/test-cd.c                  |  234 +++++++
 plugins/audiocd/update-from-egg.sh         |   25 -
 po/POTFILES.in                             |    8 +-
 28 files changed, 1744 insertions(+), 3191 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 73c80e0..95f3244 100644
--- a/.gitignore
+++ b/.gitignore
@@ -126,3 +126,5 @@ doc/reference/rhythmbox.signals
 #
 widgets/test-rb-segmented-bar
 widgets/test-uri-dialog
+
+plugins/audiocd/test-cd
diff --git a/configure.ac b/configure.ac
index 76f2a67..b9ec80f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -49,8 +49,6 @@ GST_0_10_REQS=0.10.32
 GDK_PIXBUF_REQS=2.18.0
 GLIB_REQS=2.32.0
 LIBGPOD_REQS=0.6
-MUSICBRAINZ3_REQS=3.0.2
-MUSICBRAINZ4_REQS=4.0.0
 TOTEM_PLPARSER_REQS=3.2.0
 VALA_REQS=0.9.4
 AVAHI_REQS=0.6
@@ -346,46 +344,6 @@ PKG_CHECK_MODULES(GSTCDDA, gstreamer-cdda-0.10)
 AC_SUBST(GSTCDDA_LIBS)
 AC_SUBST(GSTCDDA_CFLAGS)
 
-dnl check for MusicBrainz
-have_sj_metadata_getter=no
-AC_ARG_ENABLE(musicbrainz, AC_HELP_STRING([--disable-musicbrainz],
-				[build without MusicBrainz support]))
-if test x"$enable_musicbrainz" != "xno"; then
-	PKG_CHECK_MODULES(MUSICBRAINZ3, libmusicbrainz3 >= $MUSICBRAINZ3_REQS gconf-2.0, [have_musicbrainz3=yes], [have_musicbrainz3=no])
-	AC_SUBST(MUSICBRAINZ3_CFLAGS)
-	AC_SUBST(MUSICBRAINZ3_LIBS)
-
-	PKG_CHECK_MODULES(MUSICBRAINZ4, libmusicbrainz4 >= $MUSICBRAINZ4_REQS gconf-2.0, [have_musicbrainz4=yes], [have_musicbrainz4=no])
-	AC_SUBST(MUSICBRAINZ4_CFLAGS)
-	AC_SUBST(MUSICBRAINZ4_LIBS)
-
-	if test x"$have_musicbrainz3" = xyes; then
-		oldlibs=$LIBS
-		LIBS="$LIBS $MUSICBRAINZ3_LIBS"
-		AC_CHECK_FUNCS(mb_extract_uuid)
-		LIBS="$oldlibs"
-
-		AC_DEFINE([HAVE_MUSICBRAINZ3], 1, [Whether libmusicbrainz3 is available])
-		have_sj_metadata_getter=yes
-	fi
-
-	if test x"$have_musicbrainz4" = xyes; then
-		AC_DEFINE([HAVE_MUSICBRAINZ4], 1, [Whether libmusicbrainz4 is available])
-		have_sj_metadata_getter=yes
-	fi
-
-	if test x"$have_sj_metadata_getter" = xyes; then
-		AC_DEFINE([HAVE_SJ_METADATA_GETTER], 1, [Whether to use the sound-juicer metadata getter code])
-	else
-		if test x"$enable_musicbrainz" = xyes; then
-			AC_MSG_ERROR([MusicBrainz requested, but neither libmusicbrainz3 nor libmusicbrainz4 are available])
-		fi
-	fi
-fi
-AM_CONDITIONAL([HAVE_MUSICBRAINZ3], [test "x$have_musicbrainz3" = "xyes"])
-AM_CONDITIONAL([HAVE_MUSICBRAINZ4], [test "x$have_musicbrainz4" = "xyes"])
-AM_CONDITIONAL([HAVE_SJ_METADATA_GETTER], [test "x$have_sj_metadata_getter" = "xyes"])
-
 AC_PATH_XTRA
 CFLAGS="$CFLAGS $X_CFLAGS"
 #LIBS=$X_LIBS
@@ -481,8 +439,6 @@ AC_SUBST(mkdir_p) if test x"$mkdir_p" = "x"; then
 fi
 AC_SUBST(MKINSTALLDIRS)
 
-AM_GCONF_SOURCE_2
-
 dnl LIRC
 AC_ARG_ENABLE(lirc,
 	AC_HELP_STRING([--enable-lirc],[enable lirc support]))
@@ -902,12 +858,6 @@ else
 	AC_MSG_NOTICE([** Multimedia keys support is enabled])
 fi
 
-if test x"$have_musicbrainz3" = "xyes"; then
-	AC_MSG_NOTICE([** MusicBrainz support is enabled])
-else
-	AC_MSG_NOTICE([   MusicBrainz support is disabled])
-fi
-
 if test x"$use_ipod" = xyes; then
 	AC_MSG_NOTICE([** iPod integration enabled])
 else
diff --git a/doc/reference/Makefile.am b/doc/reference/Makefile.am
index 4a4cf5c..665d3c2 100644
--- a/doc/reference/Makefile.am
+++ b/doc/reference/Makefile.am
@@ -74,14 +74,6 @@ IGNORE_HFILES= \
 	rb-sync-state-ui.h \
 	\
 	rb-audiocd-source.h \
-	sj-error.h \
-	sj-metadata-getter.h \
-	sj-metadata-gvfs.h \
-	sj-metadata-marshal.h \
-	sj-metadata-musicbrainz.h \
-	sj-metadata-musicbrainz3.h \
-	sj-metadata.h \
-	sj-structures.h \
 	rb-audioscrobbler-entry.h \
 	rb-lastfm-play-order.h \
 	rb-audioscrobbler.h \
diff --git a/plugins/audiocd/Makefile.am b/plugins/audiocd/Makefile.am
index e757d2d..7ad773e 100644
--- a/plugins/audiocd/Makefile.am
+++ b/plugins/audiocd/Makefile.am
@@ -3,9 +3,13 @@ plugindatadir = $(PLUGINDATADIR)/audiocd
 plugin_LTLIBRARIES = libaudiocd.la
 
 libaudiocd_la_SOURCES =					\
+	rb-audiocd-info.c				\
+	rb-audiocd-info.h				\
 	rb-audiocd-plugin.c				\
 	rb-audiocd-source.c				\
-	rb-audiocd-source.h
+	rb-audiocd-source.h				\
+	rb-musicbrainz-lookup.c				\
+	rb-musicbrainz-lookup.h
 
 libaudiocd_la_LDFLAGS = $(PLUGIN_LIBTOOL_FLAGS)
 libaudiocd_la_LIBTOOLFLAGS = --tag=disable-static
@@ -22,12 +26,9 @@ INCLUDES = 						\
 	-I$(top_srcdir)/lib                             \
 	-I$(top_srcdir)/lib/libmediaplayerid            \
 	-I$(top_srcdir)/metadata                       	\
-	-I$(top_srcdir)/player                       	\
 	-I$(top_srcdir)/rhythmdb                       	\
 	-I$(top_srcdir)/widgets                    	\
 	-I$(top_srcdir)/sources                    	\
-	-I$(top_srcdir)/iradio                    	\
-	-I$(top_srcdir)/podcast                    	\
 	-I$(top_srcdir)/remote				\
 	-I$(top_builddir)/remote			\
 	-I$(top_srcdir)/plugins				\
@@ -42,86 +43,34 @@ INCLUDES = 						\
 	-DUSE_TOTEM_PL_PARSER				\
 	-D_BSD_SOURCE
 
-if HAVE_MUSICBRAINZ3
-libaudiocd_la_LIBADD +=$(MUSICBRAINZ3_LIBS)
-INCLUDES += $(MUSICBRAINZ3_CFLAGS)
-endif
-if HAVE_MUSICBRAINZ4
-libaudiocd_la_LIBADD +=$(MUSICBRAINZ4_LIBS)
-INCLUDES += $(MUSICBRAINZ4_CFLAGS)
-endif
-
 libaudiocd_la_LIBADD += $(NULL)
 
 
 gtkbuilderdir = $(plugindatadir)
 gtkbuilder_DATA = 					\
-	album-info.ui					\
-	multiple-album.ui
+	album-info.ui
 
 uixmldir = $(plugindatadir)
 uixml_DATA = audiocd-ui.xml
 
+noinst_PROGRAMS = test-cd
+
+test_cd_SOURCES = 					\
+	test-cd.c					\
+	rb-audiocd-info.c				\
+	rb-audiocd-info.h				\
+	rb-musicbrainz-lookup.c				\
+	rb-musicbrainz-lookup.h
+test_cd_LDADD = $(RHYTHMBOX_LIBS) $(GSTCDDA_LIBS)
+
 plugin_in_files = audiocd.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 = $(gtkbuilder_DATA) $(uixml_DATA) $(plugin_in_files) sj-metadata-marshal.list
-
-SJ_FILES =									\
-	sj-error.c sj-error.h							\
-	sj-metadata.c sj-metadata.h						\
-	sj-metadata-gvfs.c sj-metadata-gvfs.h					\
-	sj-metadata-getter.c sj-metadata-getter.h				\
-	sj-metadata-musicbrainz3.c sj-metadata-musicbrainz3.h			\
-	sj-metadata-musicbrainz4.c sj-metadata-musicbrainz4.h			\
-	sj-structures.c sj-structures.h						\
-	sj-metadata-marshal.list
-
-EGGDIR=$(srcdir)/../../../sound-juicer/libjuicer
-regenerate-built-sources:
-	EGGFILES="$(SJ_FILES)" EGGDIR="$(EGGDIR)" $(srcdir)/update-from-egg.sh || true
-
-MARSHALFILES = 
-
-if HAVE_SJ_METADATA_GETTER
-libaudiocd_la_SOURCES +=							\
-	sj-metadata.h sj-metadata.c						\
-	sj-metadata-getter.c sj-metadata-getter.h				\
-	sj-metadata-gvfs.c sj-metadata-gvfs.h					\
-	sj-structures.h sj-structures.c						\
-	sj-error.h sj-error.c							\
-	$(MARSHALFILES)
-
-if HAVE_MUSICBRAINZ3
-libaudiocd_la_SOURCES += sj-metadata-musicbrainz3.c sj-metadata-musicbrainz3.h
-endif
-if HAVE_MUSICBRAINZ4
-libaudiocd_la_SOURCES += sj-metadata-musicbrainz4.c sj-metadata-musicbrainz4.h
-endif
-
-MARSHALFILES += sj-metadata-marshal.h sj-metadata-marshal.c
-
-sj-metadata-marshal.h: sj-metadata-marshal.list
-	( $(GLIB_GENMARSHAL) --prefix=metadata_marshal $< \
-	--header > marshal-header.tmp \
-	&& mv marshal-header.tmp $@ ) \
-	|| ( rm -f marshal-header.tmp && exit 1 )
-
-sj-metadata-marshal.c: sj-metadata-marshal.list
-	( $(GLIB_GENMARSHAL) --prefix=metadata_marshal $< \
-	--body > marshal-source.tmp \
-	&& echo "#include \"sj-metadata-marshal.h\"" > $@ \
-	&& cat marshal-source.tmp >> $@ \
-	&& rm -f marshal-source.tmp ) \
-	|| ( rm -f marshal-source.tmp && exit 1 )
-
-endif
-
-BUILT_SOURCES = $(MARSHALFILES)
-
-CLEANFILES = $(plugin_DATA) $(BUILT_SOURCES)
+EXTRA_DIST = $(gtkbuilder_DATA) $(uixml_DATA) $(plugin_in_files)
+
+CLEANFILES = $(plugin_DATA)
 DISTCLEANFILES = $(plugin_DATA)
 
diff --git a/plugins/audiocd/rb-audiocd-info.c b/plugins/audiocd/rb-audiocd-info.c
new file mode 100644
index 0000000..46c5203
--- /dev/null
+++ b/plugins/audiocd/rb-audiocd-info.c
@@ -0,0 +1,281 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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/gi18n.h>
+#include <gst/gst.h>
+#include <gst/cdda/gstcddabasesrc.h>
+#include <gio/gio.h>
+
+#include "rb-audiocd-info.h"
+
+static gboolean
+read_gst_disc_info (RBAudioCDInfo *info, GError **error)
+{
+	GstElement *source;
+	GstElement *sink;
+	GstElement *pipeline;
+	GstFormat format;
+	GstFormat out_format;
+	GstBus *bus;
+	gint64 num_tracks;
+	gboolean done;
+	int i;
+
+	source = gst_element_make_from_uri (GST_URI_SRC, "cdda://", NULL);
+	if (source == NULL) {
+		/* if cdparanoiasrc wasn't in base and installed by default
+		 * everywhere, plugin install might be worth trying here.
+		 */
+		g_set_error_literal (error,
+				     GST_CORE_ERROR,
+				     GST_CORE_ERROR_MISSING_PLUGIN,
+				     _("Could not find a GStreamer CD source plugin"));
+		return FALSE;
+	}
+
+	g_object_set (source, "device", info->device, NULL);
+	pipeline = gst_pipeline_new (NULL);
+	sink = gst_element_factory_make ("fakesink", NULL);
+	gst_bin_add_many (GST_BIN (pipeline), source, sink, NULL);
+	gst_element_link (source, sink);
+
+	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "paranoia-mode"))
+		g_object_set (source, "paranoia-mode", 0, NULL);
+
+	gst_element_set_state (pipeline, GST_STATE_PAUSED);
+
+	bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
+	done = FALSE;
+	while (done == FALSE) {
+		GstMessage *msg;
+		GstTagList *tags;
+
+		msg = gst_bus_timed_pop (bus, 3 * GST_SECOND);
+		if (msg == NULL)
+			break;
+
+		switch (GST_MESSAGE_TYPE (msg)) {
+		case GST_MESSAGE_TAG:
+			gst_message_parse_tag (msg, &tags);
+
+			gst_tag_list_get_string (tags,
+						 GST_TAG_CDDA_MUSICBRAINZ_DISCID,
+						 &info->musicbrainz_disc_id);
+			gst_tag_list_get_string (tags,
+						 GST_TAG_CDDA_MUSICBRAINZ_DISCID_FULL,
+						 &info->musicbrainz_full_disc_id);
+
+			gst_tag_list_free (tags);
+			break;
+
+		case GST_MESSAGE_STATE_CHANGED:
+			if (GST_MESSAGE_SRC (msg) == GST_OBJECT (pipeline)) {
+				GstState oldstate;
+				GstState newstate;
+				GstState pending;
+
+				gst_message_parse_state_changed (msg, &oldstate, &newstate, &pending);
+				if (newstate == GST_STATE_PAUSED && pending == GST_STATE_VOID_PENDING)
+					done = TRUE;
+			}
+			break;
+		case GST_MESSAGE_ERROR:
+			gst_message_parse_error (msg, error, NULL);
+			done = TRUE;
+			break;
+		default:
+			break;
+		}
+
+		gst_message_unref (msg);
+	}
+
+	if (*error == NULL) {
+		format = gst_format_get_by_nick ("track");
+		out_format = format;
+		gst_element_query_duration (source, &out_format, &num_tracks);
+		info->num_tracks = num_tracks;
+
+		info->tracks = g_new0 (RBAudioCDTrack, num_tracks);
+		for (i = 0; i < num_tracks; i++) {
+			RBAudioCDTrack *track = &info->tracks[i];
+			GstCddaBaseSrcTrack *gst_track = &GST_CDDA_BASE_SRC (source)->tracks[i];
+			guint64 duration = 0;
+
+			track->is_audio = gst_track->is_audio;
+			track->track_num = gst_track->num;
+
+			gst_tag_list_get_uint64 (gst_track->tags, GST_TAG_DURATION, &duration);
+			track->duration = (duration / GST_MSECOND);
+		}
+	}
+
+	gst_element_set_state (pipeline, GST_STATE_NULL);
+	gst_object_unref (bus);
+	gst_object_unref (pipeline);
+	return (*error == NULL);
+}
+
+static void
+read_gvfs_disc_info (RBAudioCDInfo *info)
+{
+	GFile *cdda;
+	GFileInfo *fileinfo;
+	GFileEnumerator *tracks;
+	const char *attr;
+	char *uri;
+	char *dev;
+
+	dev = g_path_get_basename (info->device);
+	uri = g_strdup_printf ("cdda://%s", dev);
+	g_free (dev);
+
+	cdda = g_file_new_for_uri (uri);
+	g_free (uri);
+
+	fileinfo = g_file_query_info (cdda, "xattr::*", G_FILE_QUERY_INFO_NONE, NULL, NULL);
+	if (fileinfo == NULL) {
+		g_object_unref (cdda);
+		return;
+	}
+
+	attr = g_file_info_get_attribute_string (fileinfo, "xattr::org.gnome.audio.title");
+	if (attr != NULL) {
+		info->album = g_strdup (attr);
+	}
+	attr = g_file_info_get_attribute_string (fileinfo, "xattr::org.gnome.audio.artist");
+	if (attr != NULL) {
+		info->album_artist = g_strdup (attr);
+	}
+	attr = g_file_info_get_attribute_string (fileinfo, "xattr::org.gnome.audio.genre");
+	if (attr != NULL) {
+		info->genre = g_strdup (attr);
+	}
+
+	tracks = g_file_enumerate_children (cdda, G_FILE_ATTRIBUTE_STANDARD_NAME ",xattr::*", G_FILE_QUERY_INFO_NONE, NULL, NULL);
+	if (tracks != NULL) {
+		for (fileinfo = g_file_enumerator_next_file (tracks, NULL, NULL);
+		     fileinfo != NULL;
+		     fileinfo = g_file_enumerator_next_file (tracks, NULL, NULL)) {
+			const char *name;
+			const char *attr;
+			int track_num;
+
+			name = g_file_info_get_name (fileinfo);
+			if (name == NULL || sscanf (name, "Track %d.wav", &track_num) != 1) {
+				continue;
+			}
+
+			if (track_num < 1 || track_num > info->num_tracks) {
+				continue;
+			}
+			g_assert (track_num == info->tracks[track_num-1].track_num);
+
+			attr = g_file_info_get_attribute_string (fileinfo, "xattr::org.gnome.audio.title");
+			if (attr != NULL) {
+				info->tracks[track_num - 1].title = g_strdup (attr);
+			}
+			attr = g_file_info_get_attribute_string (fileinfo, "xattr::org.gnome.audio.artist");
+			if (attr != NULL) {
+				info->tracks[track_num - 1].artist = g_strdup (attr);
+			}
+		}
+	}
+	g_object_unref (tracks);
+
+	g_object_unref (cdda);
+}
+
+static void
+audiocd_info_thread (GSimpleAsyncResult *result, GObject *object, GCancellable *cancellable)
+{
+	RBAudioCDInfo *info;
+	GError *error = NULL;
+
+	info = g_simple_async_result_get_op_res_gpointer (result);
+
+	if (read_gst_disc_info (info, &error)) {
+		read_gvfs_disc_info (info);
+	} else {
+		rb_audiocd_info_free (info);
+		g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
+		g_simple_async_result_take_error (result, error);
+	}
+}
+
+void
+rb_audiocd_info_free (RBAudioCDInfo *info)
+{
+	int i;
+
+	g_free (info->device);
+	g_free (info->musicbrainz_disc_id);
+	g_free (info->musicbrainz_full_disc_id);
+	g_free (info->album);
+	g_free (info->genre);
+	g_free (info->album_artist);
+
+	for (i = 0; i < info->num_tracks; i++) {
+		g_free (info->tracks[i].artist);
+		g_free (info->tracks[i].title);
+	}
+	g_free (info->tracks);
+	g_free (info);
+}
+
+void
+rb_audiocd_info_get (const char *device,
+		     GCancellable *cancellable,
+		     GAsyncReadyCallback callback,
+		     gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+	RBAudioCDInfo *info;
+
+	result = g_simple_async_result_new (NULL, callback, user_data, rb_audiocd_info_get);
+	g_simple_async_result_set_check_cancellable (result, cancellable);
+
+	info = g_new0 (RBAudioCDInfo, 1);
+	info->device = g_strdup (device);
+	g_simple_async_result_set_op_res_gpointer (result, info, NULL);
+
+	g_simple_async_result_run_in_thread (result, audiocd_info_thread, G_PRIORITY_DEFAULT, cancellable);
+}
+
+RBAudioCDInfo *
+rb_audiocd_info_finish (GAsyncResult *result,
+			GError **error)
+{
+	g_return_val_if_fail (g_simple_async_result_is_valid (result, NULL, rb_audiocd_info_get),
+			      NULL);
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+
+	return g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+}
diff --git a/plugins/audiocd/rb-audiocd-info.h b/plugins/audiocd/rb-audiocd-info.h
new file mode 100644
index 0000000..8e63534
--- /dev/null
+++ b/plugins/audiocd/rb-audiocd-info.h
@@ -0,0 +1,68 @@
+/*
+ *  Copyright (C) 2012 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_AUDIOCD_INFO_H
+#define RB_AUDIOCD_INFO_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+	gboolean is_audio;
+	int track_num;
+	int duration;		/* milliseconds? */
+	char *artist;
+	char *title;
+} RBAudioCDTrack;
+
+typedef struct {
+	char *device;
+
+	char *musicbrainz_disc_id;
+	char *musicbrainz_full_disc_id;
+
+	char *album;
+	char *genre;
+	char *album_artist;
+
+	int num_tracks;
+	RBAudioCDTrack *tracks;
+} RBAudioCDInfo;
+
+RBAudioCDInfo *		rb_audiocd_info_finish		(GAsyncResult *result,
+							 GError **error);
+
+void			rb_audiocd_info_get		(const char *device,
+							 GCancellable *cancellable,
+							 GAsyncReadyCallback callback,
+							 gpointer user_data);
+
+void			rb_audiocd_info_free		(RBAudioCDInfo *info);
+
+#endif /* RB_AUDIOCD_INFO_H */
diff --git a/plugins/audiocd/rb-audiocd-source.c b/plugins/audiocd/rb-audiocd-source.c
index 66c3bc5..68894c3 100644
--- a/plugins/audiocd/rb-audiocd-source.c
+++ b/plugins/audiocd/rb-audiocd-source.c
@@ -26,18 +26,13 @@
  *
  */
 
-/*
- * TODO
- *    * save user-edited metadata somewhere (use S-J stuff?)
- */
-
 #include "config.h"
 
 #include <string.h>
+
 #include <gtk/gtk.h>
 #include <glib/gi18n.h>
 #include <gst/gst.h>
-#include <gst/cdda/gstcddabasesrc.h>
 
 #include "rhythmdb.h"
 #include "rb-shell.h"
@@ -50,11 +45,8 @@
 #include "rb-file-helpers.h"
 #include "rb-source-toolbar.h"
 #include "rb-shell-player.h"
-
-#ifdef HAVE_SJ_METADATA_GETTER
-#include "sj-metadata-getter.h"
-#include "sj-structures.h"
-#endif
+#include "rb-audiocd-info.h"
+#include "rb-musicbrainz-lookup.h"
 
 enum
 {
@@ -76,20 +68,15 @@ static guint impl_want_uri (RBSource *source, const char *uri);
 static gboolean impl_uri_is_source (RBSource *source, const char *uri);
 static RBEntryView *impl_get_entry_view (RBSource *source);
 
-static gpointer rb_audiocd_load_songs (RBAudioCdSource *source);
-static void rb_audiocd_load_metadata (RBAudioCdSource *source, RhythmDB *db);
-static void rb_audiocd_load_metadata_cancel (RBAudioCdSource *source);
-
 static gboolean update_artist_cb (GtkWidget *widget, GdkEventFocus *event, RBAudioCdSource *source);
 static gboolean update_artist_sort_cb (GtkWidget *widget, GdkEventFocus *event, RBAudioCdSource *source);
 static gboolean update_album_cb (GtkWidget *widget, GdkEventFocus *event, RBAudioCdSource *source);
 static gboolean update_genre_cb (GtkWidget *widget, GdkEventFocus *event, RBAudioCdSource *source);
 static gboolean update_year_cb (GtkWidget *widget, GdkEventFocus *event, RBAudioCdSource *source);
 static gboolean update_disc_number_cb (GtkWidget *widget, GdkEventFocus *event, RBAudioCdSource *source);
-#if defined(HAVE_SJ_METADATA_GETTER)
-static void info_bar_response_cb (GtkInfoBar *info_bar, gint response_id, RBAudioCdSource *source);
-#endif
 
+static void rb_audiocd_source_load_disc_info (RBAudioCdSource *source);
+static gboolean rb_audiocd_source_load_metadata (RBAudioCdSource *source);
 static void reload_metadata_cmd (GtkAction *action, RBAudioCdSource *source);
 static void copy_tracks_cmd (GtkAction *action, RBAudioCdSource *source);
 
@@ -114,11 +101,14 @@ struct _RBAudioCdSourcePrivate
 	GVolume *volume;
 
 	gchar *device_path;
+	RBAudioCDInfo *disc_info;
+	RBMusicBrainzData *mb_data;
+	GList *releases;
 	GList *tracks;
 
-	GstElement *pipeline;
-	GstElement *cdda;
-	GstElement *fakesink;
+	GCancellable *cancel_disc_info;
+	GtkWidget *infogrid;
+	GtkWidget *info_bar;
 
 	RBEntryView *entry_view;
 	GtkWidget *artist_entry;
@@ -128,19 +118,6 @@ struct _RBAudioCdSourcePrivate
 	GtkWidget *genre_entry;
 	GtkWidget *disc_number_entry;
 
-#ifdef HAVE_SJ_METADATA_GETTER
-	SjMetadataGetter *metadata;
-	GtkWidget *multiple_album_dialog;
-	GtkWidget *albums_listview;
-	GtkListStore *albums_store;
-	GList *albums;
-
-	GtkWidget *info_bar;
-	GtkWidget *info_bar_label;
-
-	char *submit_url;
-#endif
-
 	GtkActionGroup *action_group;
 };
 
@@ -161,10 +138,6 @@ GType rb_audiocd_entry_type_get_type (void);
 
 G_DEFINE_DYNAMIC_TYPE (RBAudioCdEntryType, rb_audiocd_entry_type, RHYTHMDB_TYPE_ENTRY_TYPE);
 
-#ifdef HAVE_SJ_METADATA_GETTER
-static void multiple_album_dialog (RBAudioCdSource *source, GList *albums);
-#endif
-
 static GtkActionEntry rb_audiocd_source_actions[] = {
 	{ "AudioCdCopyTracks", GTK_STOCK_CDROM, N_("_Extract to Library"), NULL,
 	  N_("Copy tracks to the library"),
@@ -265,13 +238,16 @@ rb_audiocd_source_finalize (GObject *object)
 	RBAudioCdSource *source = RB_AUDIOCD_SOURCE (object);
 
 	g_free (source->priv->device_path);
-#ifdef HAVE_SJ_METADATA_GETTER
-	g_free (source->priv->submit_url);
-#endif
 
 	if (source->priv->tracks) {
 		g_list_free (source->priv->tracks);
-		source->priv->tracks = NULL;
+	}
+
+	if (source->priv->disc_info) {
+		rb_audiocd_info_free (source->priv->disc_info);
+	}
+	if (source->priv->mb_data) {
+		rb_musicbrainz_data_free (source->priv->mb_data);
 	}
 
 	G_OBJECT_CLASS (rb_audiocd_source_parent_class)->finalize (object);
@@ -287,11 +263,6 @@ rb_audiocd_source_dispose (GObject *object)
 		source->priv->action_group = NULL;
 	}
 
-	if (source->priv->pipeline) {
-		gst_object_unref (GST_OBJECT (source->priv->pipeline));
-		source->priv->pipeline = NULL;
-	}
-
 	G_OBJECT_CLASS (rb_audiocd_source_parent_class)->dispose (object);
 }
 
@@ -332,7 +303,6 @@ rb_audiocd_source_constructed (GObject *object)
 	GtkBuilder *builder;
 	GtkWidget *grid;
 	GtkWidget *widget;
-	GtkWidget *infogrid;
 	GtkAction *action;
 	GObject *plugin;
 	RBShell *shell;
@@ -344,15 +314,12 @@ rb_audiocd_source_constructed (GObject *object)
 	RBSourceToolbar *toolbar;
 	char *ui_file;
 	int toggle_width;
-#if defined(HAVE_SJ_METADATA_GETTER)
-	GtkWidget *box;
-	char *message;
-#endif
 
 	RB_CHAIN_GOBJECT_METHOD (rb_audiocd_source_parent_class, constructed, object);
 	source = RB_AUDIOCD_SOURCE (object);
 
 	rb_device_source_set_display_details (RB_DEVICE_SOURCE (source));
+	source->priv->device_path = g_volume_get_identifier (source->priv->volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
 
 	g_object_get (source, "shell", &shell, NULL);
 	g_object_get (shell,
@@ -377,10 +344,6 @@ rb_audiocd_source_constructed (GObject *object)
 	   for Copy to Library action. */
 	g_object_set (action, "short-label", _("Extract"), NULL);
 
-#if !defined(HAVE_SJ_METADATA_GETTER)
-	action = gtk_action_group_get_action (source->priv->action_group, "AudioCdSourceReloadMetadata");
-	g_object_set (action, "visible", FALSE, NULL);
-#endif
 	/* source toolbar */
 	toolbar = rb_source_toolbar_new (RB_SOURCE (source), ui_manager);
 	g_object_unref (ui_manager);
@@ -458,28 +421,8 @@ rb_audiocd_source_constructed (GObject *object)
 	builder = rb_builder_load (ui_file, NULL);
 	g_free (ui_file);
 
-	infogrid = GTK_WIDGET (gtk_builder_get_object (builder, "album_info"));
-	g_assert (infogrid != NULL);
-
-#if defined(HAVE_SJ_METADATA_GETTER)
-	/* Info bar for non-Musicbrainz data */
-	source->priv->info_bar = gtk_info_bar_new_with_buttons (_("S_ubmit Album"), GTK_RESPONSE_OK,
-								_("Hide"), GTK_RESPONSE_CANCEL,
-								NULL);
-	message = g_strdup_printf ("<b>%s</b>\n%s", _("Could not find this album on MusicBrainz."),
-				   _("You can improve the MusicBrainz database by adding this album."));
-	source->priv->info_bar_label = gtk_label_new (NULL);
-	gtk_label_set_markup (GTK_LABEL (source->priv->info_bar_label), message);
-	gtk_label_set_justify (GTK_LABEL (source->priv->info_bar_label), GTK_JUSTIFY_LEFT);
-	g_free (message);
-	box = gtk_info_bar_get_content_area (GTK_INFO_BAR (source->priv->info_bar));
-	gtk_container_add (GTK_CONTAINER (box), source->priv->info_bar_label);
-	gtk_widget_show_all (box);
-	gtk_widget_set_no_show_all (source->priv->info_bar, TRUE);
-	g_signal_connect (G_OBJECT (source->priv->info_bar), "response",
-			  G_CALLBACK (info_bar_response_cb), source);
-	gtk_grid_attach (GTK_GRID (infogrid), source->priv->info_bar, 0, 0, 2, 1);
-#endif
+	source->priv->infogrid = GTK_WIDGET (gtk_builder_get_object (builder, "album_info"));
+	g_assert (source->priv->infogrid != NULL);
 
 	source->priv->artist_entry = GTK_WIDGET (gtk_builder_get_object (builder, "artist_entry"));
 	source->priv->artist_sort_entry = GTK_WIDGET (gtk_builder_get_object (builder, "artist_sort_entry"));
@@ -498,7 +441,7 @@ rb_audiocd_source_constructed (GObject *object)
 	grid = gtk_grid_new ();
 	gtk_grid_set_row_spacing (GTK_GRID (grid), 6);
 	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (toolbar), 0, 0, 1, 1);
-	gtk_grid_attach (GTK_GRID (grid), infogrid, 0, 1, 1, 1);
+	gtk_grid_attach (GTK_GRID (grid), source->priv->infogrid, 0, 1, 1, 1);
 	gtk_grid_attach (GTK_GRID (grid), GTK_WIDGET (source->priv->entry_view), 0, 2, 1, 1);
 	gtk_widget_set_margin_top (GTK_WIDGET (grid), 6);
 	g_object_unref (builder);
@@ -508,7 +451,9 @@ rb_audiocd_source_constructed (GObject *object)
 	gtk_widget_show_all (grid);
 	gtk_container_add (GTK_CONTAINER (source), grid);
 
-	g_thread_new ("audiocd-scan", (GThreadFunc)rb_audiocd_load_songs, g_object_ref (source));
+	source->priv->cancel_disc_info = g_cancellable_new ();
+	rb_audiocd_source_load_disc_info (source);
+
 	g_object_unref (db);
 	g_object_unref (shell_player);
 }
@@ -617,558 +562,601 @@ entry_set_string_prop (RhythmDB *db,
 	g_value_unset (&value);
 }
 
-static RhythmDBEntry *
-rb_audiocd_create_track_entry (RBAudioCdSource *source,
-			       RhythmDB *db,
-			       guint track_number)
+static void
+clear_info_bar (RBAudioCdSource *source)
 {
-	RhythmDBEntry *entry;
-	char *audio_path;
-	guint64 duration;
-	GValue value = {0, };
-	gchar *str;
-	RhythmDBEntryType *entry_type;
-	RBAudioCDEntryData *extra_data;
+	if (source->priv->info_bar != NULL) {
+		gtk_widget_hide (source->priv->info_bar);
+		gtk_container_remove (GTK_CONTAINER (source->priv->infogrid), source->priv->info_bar);
+		source->priv->info_bar = NULL;
+	}
+}
 
-	audio_path = g_strdup_printf ("cdda://%d#%s", track_number, source->priv->device_path);
+static void
+show_info_bar (RBAudioCdSource *source, GtkWidget *info_bar)
+{
+	clear_info_bar (source);
 
-	g_object_get (source, "entry-type", &entry_type, NULL);
-	rb_debug ("Audio CD - create entry for track %d from %s", track_number, audio_path);
-	entry = rhythmdb_entry_new (db, entry_type, audio_path);
-	g_object_unref (entry_type);
-	if (entry == NULL) {
-		g_free (audio_path);
-		return NULL;
-	}
+	gtk_widget_show_all (info_bar);
+	gtk_grid_attach (GTK_GRID (source->priv->infogrid), info_bar, 0, 0, 2, 1);
+	source->priv->info_bar = info_bar;
+}
 
-	/* generate track # */
-	g_value_init (&value, G_TYPE_ULONG);
-	g_value_set_ulong (&value, track_number);
-	rhythmdb_entry_set (db, entry,
-			    RHYTHMDB_PROP_TRACK_NUMBER,
-			    &value);
-	g_value_unset (&value);
 
-	/* generate track name */
-	g_value_init (&value, G_TYPE_STRING);
-	str = g_strdup_printf (_("Track %u"), track_number);
-	g_value_take_string (&value, str);
-	rhythmdb_entry_set (db, entry,
-			    RHYTHMDB_PROP_TITLE,
-			    &value);
-	g_value_unset (&value);
+static void
+submit_info_bar_response_cb (GtkInfoBar *info_bar, gint response_id, RBAudioCdSource *source)
+{
+	GError *error = NULL;
 
-	/* determine the duration
-	 * FIXME: http://bugzilla.gnome.org/show_bug.cgi?id=551011 */
-	if (gst_tag_list_get_uint64 (GST_CDDA_BASE_SRC(source->priv->cdda)->tracks[track_number - 1].tags, GST_TAG_DURATION, &duration)) {
-		g_value_init (&value, G_TYPE_ULONG);
-		g_value_set_ulong (&value, (gulong)(duration / GST_SECOND));
-		rhythmdb_entry_set (db, entry,
-				    RHYTHMDB_PROP_DURATION,
-				    &value);
-		g_value_unset (&value);
-	} else {
-		g_warning ("Failed to query cd track duration");
+	if (response_id == GTK_RESPONSE_OK) {
+		char *submit_url;
+
+		submit_url = rb_musicbrainz_create_submit_url (
+			source->priv->disc_info->musicbrainz_disc_id,
+			source->priv->disc_info->musicbrainz_full_disc_id);
+
+		if (!gtk_show_uri (NULL, submit_url, GDK_CURRENT_TIME, &error)) {
+			rb_debug ("Could not launch submit URL %s: %s", submit_url, error->message);
+			g_error_free (error);
+		}
+		g_free (submit_url);
 	}
 
-	entry_set_string_prop (db, entry, RHYTHMDB_PROP_ARTIST, FALSE, NULL);
-	entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM, FALSE, NULL);
-	entry_set_string_prop (db, entry, RHYTHMDB_PROP_GENRE, FALSE, NULL);
-	entry_set_string_prop (db, entry, RHYTHMDB_PROP_MEDIA_TYPE, TRUE, "audio/x-raw-int");
+	clear_info_bar (source);
+}
 
-	extra_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBAudioCDEntryData);
-	extra_data->extract = TRUE;
+static void
+show_submit_info_bar (RBAudioCdSource *source)
+{
+	GtkWidget *info_bar;
+	GtkWidget *label;
+	GtkWidget *box;
+	char *message;
 
-	rhythmdb_commit (db);
-	g_free (audio_path);
+	rb_debug ("showing musicbrainz submit info bar");
+
+	info_bar = gtk_info_bar_new_with_buttons (_("S_ubmit Album"), GTK_RESPONSE_OK,
+						  _("Hide"), GTK_RESPONSE_CANCEL,
+						  NULL);
+
+	message = g_strdup_printf ("<b>%s</b>\n%s", _("Could not find this album on MusicBrainz."),
+				   _("You can improve the MusicBrainz database by adding this album."));
+	label = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (label), message);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+	g_free (message);
 
-	return entry;
+	box = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
+	gtk_container_add (GTK_CONTAINER (box), label);
+
+	g_signal_connect (G_OBJECT (info_bar), "response",
+			  G_CALLBACK (submit_info_bar_response_cb), source);
+	show_info_bar (source, info_bar);
 }
 
-static gboolean
-rb_audiocd_get_cd_info (RBAudioCdSource *source,
-			gint64 *num_tracks)
+static void
+mb_error_info_bar_response_cb (GtkInfoBar *info_bar, gint response_id, RBAudioCdSource *source)
 {
-	GstFormat fmt = gst_format_get_by_nick ("track");
-	GstFormat out_fmt = fmt;
-	if (!gst_element_query_duration (source->priv->cdda, &out_fmt, num_tracks) || out_fmt != fmt) {
-		return FALSE;
+	if (response_id == GTK_RESPONSE_OK) {
+		rb_audiocd_source_load_metadata (source);
 	}
-
-	return TRUE;
+	clear_info_bar (source);
 }
 
-static gboolean
-rb_audiocd_scan_songs (RBAudioCdSource *source,
-		       RhythmDB *db)
+static void
+show_lookup_error_info_bar (RBAudioCdSource *source, GError *error)
 {
-	gint64 i, num_tracks;
-        GstStateChangeReturn ret;
-	gboolean ok = TRUE;
-
-	ret = gst_element_set_state (source->priv->pipeline, GST_STATE_PAUSED);
-	if (ret == GST_STATE_CHANGE_ASYNC) {
-		ret = gst_element_get_state (source->priv->pipeline, NULL, NULL, 3 * GST_SECOND);
-	}
-        if (ret == GST_STATE_CHANGE_FAILURE) {
-		gdk_threads_enter ();
-		rb_error_dialog (NULL, _("Couldn't load Audio CD"),
-					_("Rhythmbox couldn't access the CD."));
-		gdk_threads_leave ();
-		ok = FALSE;
-	}
+	GtkWidget *info_bar;
+	GtkWidget *label;
+	GtkWidget *box;
+	char *message;
 
-	if (ok && !rb_audiocd_get_cd_info (source, &num_tracks)) {
-		gdk_threads_enter ();
-		rb_error_dialog (NULL, _("Couldn't load Audio CD"),
-					_("Rhythmbox couldn't read the CD information."));
-		gdk_threads_leave ();
-		ok = FALSE;
-	}
+	rb_debug ("showing musicbrainz error info bar");
 
-	if (ok) {
-		rb_debug ("importing Audio Cd %s - %d tracks", source->priv->device_path, (int)num_tracks);
-		for (i = 1; i <= num_tracks; i++) {
-			RhythmDBEntry* entry = rb_audiocd_create_track_entry (source, db, i);
+	info_bar = gtk_info_bar_new_with_buttons (_("_Retry"), GTK_RESPONSE_OK,
+						  _("_Hide"), GTK_RESPONSE_CANCEL,
+						  NULL);
 
-			if (entry)
-				source->priv->tracks = g_list_prepend (source->priv->tracks, entry);
-			else
-				g_warning ("Could not create audio cd track entry");
-		}
-		source->priv->tracks = g_list_reverse (source->priv->tracks);
-	}
+	message = g_strdup_printf ("<b>%s</b>\n%s", _("Could not search MusicBrainz for album details."),
+				   error->message);
+	label = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (label), message);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+	g_free (message);
 
-	if (gst_element_set_state (source->priv->pipeline, GST_STATE_NULL) == GST_STATE_CHANGE_FAILURE) {
-		rb_debug ("failed to set cd state");
-	}
+	box = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
+	gtk_container_add (GTK_CONTAINER (box), label);
 
-	return ok;
+	g_signal_connect (G_OBJECT (info_bar), "response",
+			  G_CALLBACK (mb_error_info_bar_response_cb), source);
+	show_info_bar (source, info_bar);
 }
 
+static void
+cd_error_info_bar_response_cb (GtkInfoBar *info_bar, gint response_id, RBAudioCdSource *source)
+{
+	if (response_id == GTK_RESPONSE_OK) {
+		rb_audiocd_source_load_disc_info (source);
+	}
+	clear_info_bar (source);
+}
 
 static void
-reload_metadata_cmd (GtkAction *action, RBAudioCdSource *source)
+show_cd_error_info_bar (RBAudioCdSource *source, GError *error)
 {
-#ifdef HAVE_SJ_METADATA_GETTER
-	RhythmDB *db;
+	GtkWidget *info_bar;
+	GtkWidget *label;
+	GtkWidget *box;
+	char *message;
 
-	g_return_if_fail (RB_IS_AUDIOCD_SOURCE (source));
+	rb_debug ("showing cd read error info bar");
 
-	db = get_db_for_source (RB_AUDIOCD_SOURCE (source));
-	rb_audiocd_load_metadata (RB_AUDIOCD_SOURCE (source), db);
-	g_object_unref (db);
-#endif
+	info_bar = gtk_info_bar_new_with_buttons (_("_Retry"), GTK_RESPONSE_OK,
+						  _("_Hide"), GTK_RESPONSE_CANCEL,
+						  NULL);
+
+	message = g_strdup_printf ("<b>%s</b>\n%s", _("Could not read the CD device."),
+				   error->message);
+	label = gtk_label_new (NULL);
+	gtk_label_set_markup (GTK_LABEL (label), message);
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+	g_free (message);
+
+	box = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
+	gtk_container_add (GTK_CONTAINER (box), label);
+
+	g_signal_connect (G_OBJECT (info_bar), "response",
+			  G_CALLBACK (cd_error_info_bar_response_cb), source);
+	show_info_bar (source, info_bar);
 }
 
-#ifdef HAVE_SJ_METADATA_GETTER
 
 static void
-apply_album_metadata (RBAudioCdSource *source, AlbumDetails *album)
+apply_musicbrainz_release (RBAudioCdSource *source, RBMusicBrainzData *release)
 {
+	RBMusicBrainzData *medium;
 	RhythmDB *db;
-	GValue true_value = {0,};
-	GList *cd_track;
-
-	db = get_db_for_source (source);
-
-	g_value_init (&true_value, G_TYPE_BOOLEAN);
-	g_value_set_boolean (&true_value, TRUE);
-
-	if (album->metadata_source != SOURCE_MUSICBRAINZ) {
-		source->priv->submit_url = sj_metadata_getter_get_submit_url (source->priv->metadata);
-		if (source->priv->submit_url != NULL)
-			gtk_widget_show (source->priv->info_bar);
+	GList *l;
+	const char *value;
+	int disc_num = 0;
+	gulong release_date = 0;
+	const char *album;
+	const char *album_id;
+	const char *album_artist;
+	const char *album_artist_id;
+	const char *album_artist_sortname;
+
+	medium = rb_musicbrainz_data_find_child (release,
+						 "disc-id",
+						 source->priv->disc_info->musicbrainz_disc_id);
+	g_assert (medium != NULL);
+
+	/* set album stuff in widgets */
+	album = rb_musicbrainz_data_get_attr_value (release, RB_MUSICBRAINZ_ATTR_ALBUM);
+	if (album != NULL) {
+		rb_debug ("album title: %s", album);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->album_entry), album);
+		g_object_set (source, "name", album, NULL);
 	}
 
-	if (album->metadata_source == SOURCE_FALLBACK) {
-		rb_debug ("ignoring CD metadata from fallback source");
-		g_object_unref (source->priv->metadata);
-		source->priv->metadata = NULL;
-		g_object_unref (db);
-		g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
-		return;
+	album_artist = rb_musicbrainz_data_get_attr_value (release, RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST);
+	if (album_artist != NULL) {
+		rb_debug ("album artist: %s", album_artist);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->artist_entry), album_artist);
 	}
 
-	if (album->artist != NULL) {
-		gtk_entry_set_text (GTK_ENTRY (source->priv->artist_entry), album->artist);
+	album_artist_sortname = rb_musicbrainz_data_get_attr_value (release, RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST_SORTNAME);
+	if (album_artist_sortname != NULL) {
+		rb_debug ("album artist sortname: %s", album_artist_sortname);
+		gtk_entry_set_text (GTK_ENTRY (source->priv->artist_sort_entry), album_artist_sortname);
 	}
-	if (album->artist_sortname != NULL) {
-		gtk_entry_set_text (GTK_ENTRY (source->priv->artist_sort_entry), album->artist_sortname);
-	}
-	if (album->title != NULL) {
-		gtk_entry_set_text (GTK_ENTRY (source->priv->album_entry), album->title);
-	}
-	if (album->release_date != NULL) {
-		char *year;
-		year = g_strdup_printf ("%d", g_date_get_year (album->release_date));
-		gtk_entry_set_text (GTK_ENTRY (source->priv->year_entry), year);
-		g_free (year);
-	}
-	if (album->disc_number != 0) {
-		char *num;
-		num = g_strdup_printf ("%d", album->disc_number);
-		gtk_entry_set_text (GTK_ENTRY (source->priv->disc_number_entry), num);
-		g_free (num);
+
+	value = rb_musicbrainz_data_get_attr_value (release, RB_MUSICBRAINZ_ATTR_DATE);
+	if (value != NULL) {
+		int year = 1;
+		int month = 1;
+		int day = 1;
+
+		if (sscanf (value, "%u-%u-%u", &year, &month, &day) > 0) {
+			GDate date;
+			char *year_text;
+
+			year_text = g_strdup_printf ("%d", year);
+			gtk_entry_set_text (GTK_ENTRY (source->priv->year_entry), year_text);
+			g_free (year_text);
+
+			g_date_set_dmy (&date,
+					(day == 0) ? 1 : day,
+					(month == 0) ? 1 : month,
+					year);
+			release_date = g_date_get_julian (&date);
+		} else {
+			rb_debug ("unable to parse release date: %s", value);
+		}
 	}
-	if (album->genre != NULL) {
-		gtk_entry_set_text (GTK_ENTRY (source->priv->genre_entry), album->genre);
+
+	value = rb_musicbrainz_data_get_attr_value (medium, RB_MUSICBRAINZ_ATTR_DISC_NUMBER);
+	if (value != NULL) {
+		disc_num = strtol (value, NULL, 10);	/* 0 is ok if this fails */
+		gtk_entry_set_text (GTK_ENTRY (source->priv->disc_number_entry), value);
+		rb_debug ("disc number %d", disc_num);
 	}
 
-	g_object_set (G_OBJECT (source), "name", album->title, NULL);
-	rb_debug ("musicbrainz_albumid: %s", album->album_id);
-	rb_debug ("musicbrainz_albumartistid: %s", album->artist_id);
-	rb_debug ("album artist: %s", album->artist);
-	rb_debug ("album artist sortname: %s", album->artist_sortname);
-	rb_debug ("disc number: %d", album->disc_number);
-	rb_debug ("genre: %s", album->genre);
-
-	cd_track = source->priv->tracks;
-	while (album->tracks && cd_track) {
-		TrackDetails *track = (TrackDetails*)album->tracks->data;
-		RhythmDBEntry *entry = cd_track->data;
-		GValue value = {0, };
+	album_id = rb_musicbrainz_data_get_attr_value (release, RB_MUSICBRAINZ_ATTR_ALBUM_ID);
+	rb_debug ("musicbrainz_albumid: %s", album_id);
 
-		rb_debug ("storing metadata for %s - %s - %s", track->artist, album->title, track->title);
+	album_artist_id = rb_musicbrainz_data_get_attr_value (release, RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST_ID);
+	rb_debug ("musicbrainz_albumartistid: %s", album_artist_id);
 
-		rb_debug ("musicbrainz_trackid: %s", track->track_id);
-		rb_debug ("musicbrainz_artistid: %s", track->artist_id);
-		rb_debug ("artist sortname: %s", track->artist_sortname);
+	db = get_db_for_source (source);
 
-		/* record track info in entry*/
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_TITLE, FALSE, track->title);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ARTIST, FALSE, track->artist);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM, FALSE, album->title);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_GENRE, FALSE, album->genre);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, TRUE, track->track_id);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID, TRUE, track->artist_id);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID, TRUE, album->album_id);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID, TRUE, album->artist_id);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ARTIST_SORTNAME, TRUE, track->artist_sortname);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM_ARTIST, TRUE, album->artist);
-		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME, TRUE, album->artist_sortname);
+	/* apply things to tracks */
+	l = rb_musicbrainz_data_get_children (medium);
+	for (l = rb_musicbrainz_data_get_children (medium); l != NULL; l = l->next) {
+		RhythmDBEntry *entry;
+		RBMusicBrainzData *mb_track = l->data;
+		GList *tl;
+		GValue value = {0, };
+		const char *attr;
+		int mb_track_num;
+	
+		attr = rb_musicbrainz_data_get_attr_value (mb_track, RB_MUSICBRAINZ_ATTR_TRACK_NUMBER);
+		rb_debug ("processing musicbrainz track %s", attr);
+		mb_track_num = strtol (attr, 0, 10);
+
+		/* find matching track */
+		entry = NULL;
+		for (tl = source->priv->tracks; tl != NULL; tl = tl->next) {
+			gulong tn;
+			tn = rhythmdb_entry_get_ulong (tl->data, RHYTHMDB_PROP_TRACK_NUMBER);
+			if (tn == mb_track_num) {
+				entry = tl->data;
+				break;
+			}
+		}
 
-		g_value_init (&value, G_TYPE_ULONG);
-		g_value_set_ulong (&value, track->duration);
-		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DURATION, &value);
-		g_value_unset (&value);
+		if (entry == NULL) {
+			g_warning ("couldn't find track entry for musicbrainz track %d",
+				   mb_track_num);
+			continue;
+		}
 
-		if (album->disc_number != 0) {
+		/* apply release stuff */
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM, FALSE, album);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID, TRUE, album_id);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID, TRUE, album_artist_id);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM_ARTIST, TRUE, album_artist);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME, TRUE, album_artist_sortname);
+
+		if (release_date != 0) {
 			g_value_init (&value, G_TYPE_ULONG);
-			g_value_set_ulong (&value, album->disc_number);
-			rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DISC_NUMBER, &value);
+			g_value_set_ulong (&value, release_date);
+			rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DATE, &value);
 			g_value_unset (&value);
 		}
 
-		/*album->release_date (could potentially have multiple values)*/
-		/* in current sj-structures.h, however, it does not */
-
-		if (album->release_date) {
-			GType type = rhythmdb_get_property_type (db, RHYTHMDB_PROP_DATE);
-			g_value_init (&value, type);
-			g_value_set_ulong (&value, g_date_get_julian (album->release_date));
-			rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DATE, &value);
+		/* apply medium stuff */
+		if (disc_num != 0) {
+			g_value_init (&value, G_TYPE_ULONG);
+			g_value_set_ulong (&value, disc_num);
+			rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_DISC_NUMBER, &value);
 			g_value_unset (&value);
 		}
 
-		rhythmdb_commit (db);
+		/* apply track stuff */
+		attr = rb_musicbrainz_data_get_attr_value (mb_track, RB_MUSICBRAINZ_ATTR_TITLE);
+		rb_debug ("title: %s", attr);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_TITLE, FALSE, attr);
 
-		album->tracks = g_list_next (album->tracks);
-		cd_track = g_list_next (cd_track);
-	}
+		attr = rb_musicbrainz_data_get_attr_value (mb_track, RB_MUSICBRAINZ_ATTR_TRACK_ID);
+		rb_debug ("musicbrainz track id: %s", attr);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, TRUE, attr);
 
-	while (cd_track) {
-		/* Musicbrainz doesn't report data tracks on multisession CDs.
-		 * These aren't interesting to us anyway, so they should be hidden.
-		 */
-		RhythmDBEntry *entry = cd_track->data;
-		rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_HIDDEN, &true_value);
-		rhythmdb_commit (db);
+		attr = rb_musicbrainz_data_get_attr_value (mb_track, RB_MUSICBRAINZ_ATTR_ARTIST);
+		rb_debug ("artist: %s", attr);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ARTIST, FALSE, attr);
+
+		attr = rb_musicbrainz_data_get_attr_value (mb_track, RB_MUSICBRAINZ_ATTR_ARTIST_SORTNAME);
+		rb_debug ("artist sortname: %s", attr);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ARTIST_SORTNAME, TRUE, attr);
 
-		cd_track = g_list_next (cd_track);
+		attr = rb_musicbrainz_data_get_attr_value (mb_track, RB_MUSICBRAINZ_ATTR_ARTIST_ID);
+		rb_debug ("musicbrainz_artistid: %s", attr);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID, TRUE, attr);
 	}
 
-	g_object_unref (source->priv->metadata);
-	source->priv->metadata = NULL;
+	rhythmdb_commit (db);
 
 	g_object_unref (db);
-
-	g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
-}
-
-
-/*
- * Called by the Multiple Album dialog when the user hits return in
- * the list view
- */
-static void
-album_row_activated (GtkTreeView *treeview,
-		     GtkTreePath *arg1,
-		     GtkTreeViewColumn *arg2,
-		     gpointer user_data)
-{
-	GtkDialog *dialog = GTK_DIALOG (user_data);
-	g_assert (dialog != NULL);
-	gtk_dialog_response (dialog, GTK_RESPONSE_OK);
 }
 
 static void
-multiple_album_response_cb (GtkWidget *dialog, int response, RBAudioCdSource *source)
+album_combo_changed_cb (GtkWidget *combo, RBAudioCdSource *source)
 {
-	AlbumDetails *album;
-	GtkTreeIter iter;
-	GtkTreeSelection *selection;
-
-	/* maybe assert? */
-	if (dialog == source->priv->multiple_album_dialog) {
-		source->priv->multiple_album_dialog = NULL;
-	}
+	GList *l;
+	int active;
 
-	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (source->priv->albums_listview));
+	active = gtk_combo_box_get_active (GTK_COMBO_BOX (combo));
+	if (active == -1)
+		return;
 
-	if (response == GTK_RESPONSE_DELETE_EVENT) {
-		gtk_tree_model_get_iter_first (GTK_TREE_MODEL (source->priv->albums_store), &iter);
-	} else {
-		gtk_tree_selection_get_selected (selection, NULL, &iter);
+	l = g_list_nth (source->priv->releases, active);
+	if (l != NULL) {
+		apply_musicbrainz_release (source, l->data);
 	}
-
-	gtk_tree_model_get (GTK_TREE_MODEL (source->priv->albums_store), &iter, 2, &album, -1);
-	apply_album_metadata (source, album);
-
-	gtk_widget_destroy (dialog);
-
-	g_list_foreach (source->priv->albums, (GFunc)album_details_free, NULL);
-	g_list_free (source->priv->albums);
-	source->priv->albums = NULL;
 }
 
-/*
- * Utility function for when there are more than one albums
- * available. Borrowed from Sound Juicer.
- */
 static void
-multiple_album_dialog (RBAudioCdSource *source, GList *albums)
+show_multiple_release_info_bar (RBAudioCdSource *source)
 {
-	GtkTreeSelection *selection;
-	GtkTreeIter iter;
-	GtkBuilder *builder;
-	GtkTreeViewColumn *column;
-	GtkCellRenderer *text_renderer;
-	GObject *plugin;
-	char *builder_file;
+	GtkWidget *info_bar;
+	GtkWidget *label;
+	GtkWidget *box;
+	GtkWidget *combo;
+	GList *l;
 
-	gdk_threads_enter ();
+	rb_debug ("showing musicbrainz multiple release info bar");
 
-	g_object_get (source, "plugin", &plugin, NULL);
-	g_assert (plugin != NULL);
+	info_bar = gtk_info_bar_new ();
 
-	/* create dialog */
-	builder_file = rb_find_plugin_data_file (plugin, "multiple-album.ui");
-	g_object_unref (plugin);
+	label = gtk_label_new (_("This disc matches multiple albums. Select the correct album."));
+	gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_LEFT);
+	box = gtk_info_bar_get_content_area (GTK_INFO_BAR (info_bar));
+	gtk_container_add (GTK_CONTAINER (box), label);
 
-	if (builder_file == NULL) {
-		g_warning ("couldn't find multiple-album.ui");
-		apply_album_metadata (source, (AlbumDetails *)albums->data);
-		g_list_foreach (albums, (GFunc)album_details_free, NULL);
-		g_list_free (albums);
-		return;
-	}
+	combo = gtk_combo_box_text_new ();
+	for (l = source->priv->releases; l != NULL; l = l->next) {
+		const char *artist;
+		const char *album;
+		const char *country;
+		char *text;
 
-	source->priv->albums = albums;
-
-	builder = rb_builder_load (builder_file, NULL);
-	g_free (builder_file);
-
-	source->priv->multiple_album_dialog = GTK_WIDGET (gtk_builder_get_object (builder, "multiple_dialog"));
-	g_assert (source->priv->multiple_album_dialog != NULL);
-	gtk_window_set_transient_for (GTK_WINDOW (source->priv->multiple_album_dialog),
-				      GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (source))));
-	source->priv->albums_listview = GTK_WIDGET (gtk_builder_get_object (builder, "albums_listview"));
-
-	g_signal_connect (source->priv->albums_listview,
-			  "row-activated",
-			  G_CALLBACK (album_row_activated),
-			  source->priv->multiple_album_dialog);
-
-	/* add columns */
-	text_renderer = gtk_cell_renderer_text_new ();
-	column = gtk_tree_view_column_new_with_attributes (_("Title"),
-							   text_renderer,
-							   "text", 0,
-							   NULL);
-
-	gtk_tree_view_append_column (GTK_TREE_VIEW (source->priv->albums_listview), column);
-
-	column = gtk_tree_view_column_new_with_attributes (_("Artist"),
-							   text_renderer,
-							   "text", 1,
-							   NULL);
-	gtk_tree_view_append_column (GTK_TREE_VIEW (source->priv->albums_listview), column);
-
-	/* create model for the tree view */
-	source->priv->albums_store = gtk_list_store_new (3, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
-	gtk_tree_view_set_model (GTK_TREE_VIEW (source->priv->albums_listview), GTK_TREE_MODEL (source->priv->albums_store));
-
-	for (; albums ; albums = g_list_next (albums)) {
-		GtkTreeIter iter;
-		AlbumDetails *album = (AlbumDetails*)(albums->data);
-		gtk_list_store_append (source->priv->albums_store, &iter);
-		gtk_list_store_set (source->priv->albums_store, &iter,
-				    0, album->title,
-				    1, album->artist,
-				    2, album,
-				    -1);
+		artist = rb_musicbrainz_data_get_attr_value (l->data, RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST);
+		album = rb_musicbrainz_data_get_attr_value (l->data, RB_MUSICBRAINZ_ATTR_ALBUM);
+		country = rb_musicbrainz_data_get_attr_value (l->data, RB_MUSICBRAINZ_ATTR_COUNTRY);
+		text = g_strdup_printf ("%s - %s (%s)", artist, album, country);
+		gtk_combo_box_text_append (GTK_COMBO_BOX_TEXT (combo), NULL, text);
+		g_free (text);
 	}
 
-	selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (source->priv->albums_listview));
-	gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE);
+	g_signal_connect (combo, "changed", G_CALLBACK (album_combo_changed_cb), source);
+	gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
 
-	/* select the first row */
-	gtk_tree_model_get_iter_first (GTK_TREE_MODEL (source->priv->albums_store), &iter);
-	gtk_tree_selection_select_iter (selection, &iter);
+	box = gtk_info_bar_get_action_area (GTK_INFO_BAR (info_bar));
+	gtk_container_add (GTK_CONTAINER (box), combo);
 
-	g_signal_connect (source->priv->multiple_album_dialog,
-			  "response",
-			  G_CALLBACK (multiple_album_response_cb),
-			  source);
-	gtk_widget_grab_focus (source->priv->albums_listview);
-	gtk_widget_show_all (source->priv->multiple_album_dialog);
-
-	gdk_threads_leave ();
+	show_info_bar (source, info_bar);
 }
 
-
 static void
-metadata_cb (SjMetadataGetter *metadata,
-	     GList *albums,
-	     GError *error,
-	     RBAudioCdSource *source)
+musicbrainz_lookup_cb (GObject *obj, GAsyncResult *result, RBAudioCdSource **source_ptr)
 {
-	g_assert (metadata == source->priv->metadata);
+	RBAudioCdSource *source;
+	GError *error = NULL;
+	GList *l;
 
-	if (error != NULL) {
-		rb_debug ("Failed to load cd metadata: %s", error->message);
-		/* TODO display error to user? */
-		g_object_unref (metadata);
-		source->priv->metadata = NULL;
-		g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
+	source = *source_ptr;
+	if (source == NULL) {
+		rb_debug ("cd source was destroyed");
+		g_free (source_ptr);
 		return;
 	}
-	if (albums == NULL) {
-		rb_debug ("Musicbrainz didn't return any CD metadata, but didn't give an error");
-		g_object_unref (metadata);
-		source->priv->metadata = NULL;
-		g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
+	g_object_remove_weak_pointer (G_OBJECT (source), (gpointer *)source_ptr);
+	g_free (source_ptr);
+
+	if (source->priv->releases != NULL) {
+		g_list_free (source->priv->releases);
+		source->priv->releases = NULL;
+	}
+	if (source->priv->mb_data != NULL) {
+		rb_musicbrainz_data_free (source->priv->mb_data);
+	}
+
+	g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
+
+	source->priv->mb_data = rb_musicbrainz_lookup_finish (result, &error);
+	if (error != NULL) {
+		if (error->domain == G_IO_ERROR &&
+		    error->code == G_IO_ERROR_CANCELLED) {
+			/* do nothing */
+		} else if (error->domain == RB_MUSICBRAINZ_ERROR &&
+		    error->code == RB_MUSICBRAINZ_ERROR_NOT_FOUND) {
+			show_submit_info_bar (source);
+		} else {
+			show_lookup_error_info_bar (source, error);
+		}
+		g_clear_error (&error);
 		return;
 	}
-	if (source->priv->tracks == NULL) {
-		/* empty cd? */
-		rb_debug ("no tracks on the CD?");
-		g_object_unref (metadata);
-		source->priv->metadata = NULL;
-		g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
+
+	/* find the release and medium matching the disc id */
+	l = rb_musicbrainz_data_get_children (source->priv->mb_data);
+	if (l == NULL) {
+		show_submit_info_bar (source);
 		return;
 	}
 
-	g_free (source->priv->submit_url);
-	source->priv->submit_url = NULL;
+	for (; l != NULL; l = l->next) {
+		RBMusicBrainzData *m;
 
-	/* if we have multiple results, ask the user to pick one */
-	if (g_list_length (albums) > 1) {
-		multiple_album_dialog (source, albums);
+		m = rb_musicbrainz_data_find_child (l->data,
+						    "disc-id",
+						    source->priv->disc_info->musicbrainz_disc_id);
+		if (m == NULL)
+			continue;
+
+		source->priv->releases = g_list_append (source->priv->releases, l->data);
+	}
+
+	if (source->priv->releases == NULL) {
+		show_submit_info_bar (source);
+	} else if (g_list_length (source->priv->releases) > 1) {
+		show_multiple_release_info_bar (source);
 	} else {
-		apply_album_metadata (source, (AlbumDetails *)albums->data);
-		g_list_foreach (albums, (GFunc)album_details_free, NULL);
-		g_list_free (albums);
+		apply_musicbrainz_release (source, source->priv->releases->data);
 	}
 }
 
-static void
-metadata_cancelled_cb (SjMetadataGetter *metadata,
-		       GList *albums,
-		       GError *error,
-		       gpointer old_source)
+static gboolean
+rb_audiocd_source_load_metadata (RBAudioCdSource *source)
 {
-	/* NOTE: the source may have been finalised, and so should NOT be used*/
-	g_list_foreach (albums, (GFunc)album_details_free, NULL);
-	g_list_free (albums);
-	g_object_unref (metadata);
+	RBAudioCdSource **source_ptr;
+	const char *disc_includes[] = { "recordings", "artist-credits", NULL };
+
+	if (source->priv->disc_info->musicbrainz_disc_id == NULL) {
+		rb_debug ("not doing musicbrainz lookup as we don't have a disc id");
+		return FALSE;
+	}
+
+	source_ptr = g_new0 (RBAudioCdSource *, 1);
+	*source_ptr = source;
+	g_object_add_weak_pointer (G_OBJECT (source), (gpointer *)source_ptr);
+	rb_debug ("looking up musicbrainz data for disc %s",
+		  source->priv->disc_info->musicbrainz_disc_id);
+	rb_musicbrainz_lookup ("discid",
+			       source->priv->disc_info->musicbrainz_disc_id,
+			       disc_includes,
+			       source->priv->cancel_disc_info,
+			       (GAsyncReadyCallback) musicbrainz_lookup_cb,
+			       source_ptr);
+	return TRUE;
 }
-#endif
 
 static void
-rb_audiocd_load_metadata (RBAudioCdSource *source,
-			  RhythmDB *db)
+reload_metadata_cmd (GtkAction *action, RBAudioCdSource *source)
 {
-#ifdef HAVE_SJ_METADATA_GETTER
-	source->priv->metadata = sj_metadata_getter_new ();
-	sj_metadata_getter_set_cdrom (source->priv->metadata, source->priv->device_path);
-
-	g_signal_connect (G_OBJECT (source->priv->metadata), "metadata",
-			  G_CALLBACK (metadata_cb), source);
-	sj_metadata_getter_list_albums (source->priv->metadata, NULL);
-#else
-	g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
-#endif
+	g_return_if_fail (RB_IS_AUDIOCD_SOURCE (source));
+
+	rb_audiocd_source_load_metadata (source);
 }
 
 static void
-rb_audiocd_load_metadata_cancel (RBAudioCdSource *source)
+disc_info_cb (GObject *obj, GAsyncResult *result, RBAudioCdSource **source_ptr)
 {
-#ifdef HAVE_SJ_METADATA_GETTER
-	if (source->priv->metadata) {
-		g_signal_handlers_disconnect_by_func (G_OBJECT (source->priv->metadata),
-						      G_CALLBACK (metadata_cb), source);
-		g_signal_connect (G_OBJECT (source->priv->metadata), "metadata",
-				  G_CALLBACK (metadata_cancelled_cb), source);
+	RBAudioCdSource *source;
+	GError *error = NULL;
+	RhythmDB *db;
+	int i;
+
+	source = *source_ptr;
+	if (source == NULL) {
+		rb_debug ("cd source was destroyed");
+		g_free (source_ptr);
+		return;
 	}
-#endif
-}
+	g_object_remove_weak_pointer (G_OBJECT (source), (gpointer *)source_ptr);
+	g_free (source_ptr);
 
-static gpointer
-rb_audiocd_load_songs (RBAudioCdSource *source)
-{
-	RhythmDB *db;
-	GVolume *volume;
+	source->priv->disc_info = rb_audiocd_info_finish (result, &error);
+	if (error != NULL) {
+		if (error->domain == G_IO_ERROR &&
+		    error->code == G_IO_ERROR_CANCELLED) {
+			/* do nothing */
+		} else {
+			show_cd_error_info_bar (source, error);
+		}
+		g_clear_error (&error);
+
+		g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
+		return;
+	}
 
-	g_object_get (source, "volume", &volume, NULL);
-	source->priv->device_path = g_volume_get_identifier (volume, G_VOLUME_IDENTIFIER_KIND_UNIX_DEVICE);
-	g_object_unref (volume);
+	if (source->priv->disc_info->album_artist != NULL) {
+		gtk_entry_set_text (GTK_ENTRY (source->priv->artist_entry), source->priv->disc_info->album_artist);
+	}
+	if (source->priv->disc_info->album != NULL) {
+		gtk_entry_set_text (GTK_ENTRY (source->priv->album_entry), source->priv->disc_info->album);
+		g_object_set (source, "name", source->priv->disc_info->album, NULL);
+	}
+	if (source->priv->disc_info->genre != NULL) {
+		gtk_entry_set_text (GTK_ENTRY (source->priv->genre_entry), source->priv->disc_info->genre);
+	}
 
 	db = get_db_for_source (source);
+	for (i = 0; i < source->priv->disc_info->num_tracks; i++) {
+		RhythmDBEntry *entry;
+		char *audio_path;
+		GValue value = {0, };
+		gchar *str;
+		RhythmDBEntryType *entry_type;
+		RBAudioCDEntryData *extra_data;
+		RBAudioCDTrack *track = &source->priv->disc_info->tracks[i];
+
+		/* ignore data tracks */
+		if (track->is_audio == FALSE) {
+			rb_debug ("ignoring non-audio track %d", track->track_num);
+			continue;
+		}
 
-	rb_debug ("loading Audio CD from %s", source->priv->device_path);
-	/* create a cdda gstreamer element, to get cd info from */
-	source->priv->cdda = gst_element_make_from_uri (GST_URI_SRC, "cdda://", NULL);
-	if (!source->priv->cdda) {
-		gdk_threads_enter ();
-		rb_error_dialog (NULL, _("Couldn't load Audio CD"),
-					_("Rhythmbox could not get access to the CD device."));
-		gdk_threads_leave ();
-		goto error_out;
-	}
+		audio_path = g_strdup_printf ("cdda://%s#%d", source->priv->disc_info->device, track->track_num);
 
-	rb_debug ("cdda longname: %s", gst_element_factory_get_longname (gst_element_get_factory (source->priv->cdda)));
-	g_object_set (G_OBJECT (source->priv->cdda), "device", source->priv->device_path, NULL);
-	source->priv->pipeline = gst_pipeline_new ("pipeline");
-	source->priv->fakesink = gst_element_factory_make ("fakesink", "fakesink");
-	gst_bin_add_many (GST_BIN (source->priv->pipeline), source->priv->cdda, source->priv->fakesink, NULL);
-	gst_element_link (source->priv->cdda, source->priv->fakesink);
+		g_object_get (source, "entry-type", &entry_type, NULL);
+		rb_debug ("creating entry for track %d from %s", track->track_num, source->priv->disc_info->device);
+		entry = rhythmdb_entry_new (db, entry_type, audio_path);
+		g_object_unref (entry_type);
+		if (entry == NULL) {
+			g_warning ("unable to create entry %s", audio_path);
+			g_free (audio_path);
+			continue;
+		}
 
-	/* disable paranoia (if using cdparanoia) since we're only reading track information here.
-	 * this reduces cdparanoia's cache size, so the process is much faster.
-	 */
-	if (g_object_class_find_property (G_OBJECT_GET_CLASS (source), "paranoia-mode"))
-		g_object_set (source, "paranoia-mode", 0, NULL);
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, track->track_num);
+		rhythmdb_entry_set (db, entry,
+				    RHYTHMDB_PROP_TRACK_NUMBER,
+				    &value);
+		g_value_unset (&value);
 
-	if (rb_audiocd_scan_songs (source, db))
-		rb_audiocd_load_metadata (source, db);
+		g_value_init (&value, G_TYPE_STRING);
+		str = g_strdup_printf (_("Track %u"), track->track_num);
+		g_value_take_string (&value, str);
+		rhythmdb_entry_set (db, entry,
+				    RHYTHMDB_PROP_TITLE,
+				    &value);
+		g_value_unset (&value);
+
+		g_value_init (&value, G_TYPE_ULONG);
+		g_value_set_ulong (&value, track->duration / 1000);
+		rhythmdb_entry_set (db, entry,
+				    RHYTHMDB_PROP_DURATION,
+				    &value);
+		g_value_unset (&value);
+
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ARTIST, FALSE, track->artist);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_TITLE, FALSE, track->title);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM, FALSE, source->priv->disc_info->album);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_ALBUM_ARTIST, FALSE, source->priv->disc_info->album_artist);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_GENRE, FALSE, source->priv->disc_info->genre);
+		entry_set_string_prop (db, entry, RHYTHMDB_PROP_MEDIA_TYPE, TRUE, "audio/x-raw-int");
+
+		extra_data = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RBAudioCDEntryData);
+		extra_data->extract = TRUE;
+
+		rhythmdb_commit (db);
+		g_free (audio_path);
+
+		source->priv->tracks = g_list_prepend (source->priv->tracks, entry);
+	}
 
-error_out:
 	g_object_unref (db);
-	g_object_unref (source);
 
-	return NULL;
+	if (rb_audiocd_source_load_metadata (source) == FALSE) {
+		g_object_set (source, "load-status", RB_SOURCE_LOAD_STATUS_LOADED, NULL);
+	}
+}
+
+static void
+rb_audiocd_source_load_disc_info (RBAudioCdSource *source)
+{
+	RBAudioCdSource **source_ptr;
+
+	source_ptr = g_new0 (RBAudioCdSource *, 1);
+	*source_ptr = source;
+	g_object_add_weak_pointer (G_OBJECT (source), (gpointer *)source_ptr);
+	rb_audiocd_info_get (source->priv->device_path,
+			     source->priv->cancel_disc_info,
+			     (GAsyncReadyCallback) disc_info_cb,
+			     source_ptr);
 }
 
 static void
@@ -1180,13 +1168,9 @@ impl_delete_thyself (RBDisplayPage *page)
 
 	rb_debug ("audio cd ejected");
 
-#ifdef HAVE_SJ_METADATA_GETTER
-	if (source->priv->multiple_album_dialog != NULL) {
-		gtk_dialog_response (GTK_DIALOG (source->priv->multiple_album_dialog), GTK_RESPONSE_DELETE_EVENT);
+	if (source->priv->cancel_disc_info) {
+		g_cancellable_cancel (source->priv->cancel_disc_info);
 	}
-#endif
-
-	rb_audiocd_load_metadata_cancel (source);
 
 	db = get_db_for_source (source);
 
@@ -1372,26 +1356,6 @@ update_disc_number_cb (GtkWidget *widget, GdkEventFocus *event, RBAudioCdSource
 	return FALSE;
 }
 
-#if defined(HAVE_SJ_METADATA_GETTER)
-static void
-info_bar_response_cb (GtkInfoBar *info_bar, gint response_id, RBAudioCdSource *source)
-{
-	GError *error = NULL;
-
-	g_return_if_fail (source->priv->submit_url != NULL);
-
-	if (response_id == GTK_RESPONSE_OK) {
-		if (!gtk_show_uri (NULL, source->priv->submit_url, GDK_CURRENT_TIME, &error)) {
-			rb_debug ("Could not launch submit URL %s: %s", source->priv->submit_url, error->message);
-			g_error_free (error);
-			return;
-		}
-	}
-
-	gtk_widget_hide (source->priv->info_bar);
-}
-#endif
-
 static void
 extract_cell_data_func (GtkTreeViewColumn *column,
 			GtkCellRenderer *renderer,
diff --git a/plugins/audiocd/rb-musicbrainz-lookup.c b/plugins/audiocd/rb-musicbrainz-lookup.c
new file mode 100644
index 0000000..9bac942
--- /dev/null
+++ b/plugins/audiocd/rb-musicbrainz-lookup.c
@@ -0,0 +1,524 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2012 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 <stdlib.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <libsoup/soup.h>
+#include <libsoup/soup-gnome.h>
+
+#include "rb-musicbrainz-lookup.h"
+
+
+struct ParseAttrMap {
+	const char *path;
+	const char *xml_attr;
+	const char *attr;
+};
+
+struct _RBMusicBrainzData {
+	char *type;
+	GHashTable *attrs;
+	GList *children;
+	RBMusicBrainzData *parent;
+
+	GList *path_start;
+};
+
+typedef struct {
+	RBMusicBrainzData *current;
+	RBMusicBrainzData *root;
+
+	GQueue path;
+	const char *item;
+	GString text;
+
+	struct ParseAttrMap *map;
+} RBMusicBrainzParseContext;
+
+
+static struct ParseAttrMap root_attr_map[] = {
+	{ NULL, NULL, NULL }
+};
+
+static struct ParseAttrMap release_attr_map[] = {
+	{ "/release", "id", RB_MUSICBRAINZ_ATTR_ALBUM_ID },
+	{ "/release/asin", NULL, RB_MUSICBRAINZ_ATTR_ASIN },
+	{ "/release/country", NULL, RB_MUSICBRAINZ_ATTR_COUNTRY },
+	{ "/release/date", NULL, RB_MUSICBRAINZ_ATTR_DATE },
+	{ "/release/title", NULL, RB_MUSICBRAINZ_ATTR_ALBUM },
+	{ "/release/artist-credit/name-credit/artist", "id", RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST_ID },
+	{ "/release/artist-credit/name-credit/artist/name", NULL, RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST },
+	{ "/release/artist-credit/name-credit/artist/sort-name", NULL, RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST_SORTNAME },
+	{ NULL, NULL, NULL }
+};
+
+static struct ParseAttrMap medium_attr_map[] = {
+	{ "/medium/position", NULL, RB_MUSICBRAINZ_ATTR_DISC_NUMBER },
+	{ "/medium/track-list", "count", RB_MUSICBRAINZ_ATTR_TRACK_COUNT },
+	{ "/medium/disc-list/disc", "id", RB_MUSICBRAINZ_ATTR_DISC_ID },
+	{ NULL, NULL, NULL }
+};
+
+static struct ParseAttrMap track_attr_map[] = {
+	{ "/track/number", NULL, RB_MUSICBRAINZ_ATTR_TRACK_NUMBER },
+	{ "/track/length", NULL, RB_MUSICBRAINZ_ATTR_DURATION },
+	{ "/track/recording", "id", RB_MUSICBRAINZ_ATTR_TRACK_ID },
+	{ "/track/recording/title", NULL, RB_MUSICBRAINZ_ATTR_TITLE },
+	{ "/track/recording/artist-credit/name-credit/artist", "id", RB_MUSICBRAINZ_ATTR_ARTIST_ID },
+	{ "/track/recording/artist-credit/name-credit/artist/name", NULL, RB_MUSICBRAINZ_ATTR_ARTIST },
+	{ "/track/recording/artist-credit/name-credit/artist/sort-name", NULL, RB_MUSICBRAINZ_ATTR_ARTIST_SORTNAME },
+	{ NULL, NULL, NULL }
+};
+
+static struct ParseAttrMap relation_attr_map[] = {
+	{ "/relation", "type", RB_MUSICBRAINZ_ATTR_RELATION_TYPE },
+	{ "/relation/target", NULL, RB_MUSICBRAINZ_ATTR_RELATION_TARGET },
+	{ "/relation/artist", "id", RB_MUSICBRAINZ_ATTR_ARTIST_ID },
+	{ "/relation/artist/name", NULL, RB_MUSICBRAINZ_ATTR_ARTIST },
+	{ "/relation/artist/sortname", NULL, RB_MUSICBRAINZ_ATTR_ARTIST_SORTNAME },
+	{ "/relation/work", "id", RB_MUSICBRAINZ_ATTR_WORK_ID },
+	{ "/relation/work/title", NULL, RB_MUSICBRAINZ_ATTR_WORK_TITLE },
+	{ NULL, NULL, NULL }
+};
+
+static struct {
+	const char *name;
+	struct ParseAttrMap *map;
+} object_types[] = {
+	{ "root", root_attr_map },
+	{ "release", release_attr_map },
+	{ "medium", medium_attr_map },
+	{ "track", track_attr_map },
+	{ "relation", relation_attr_map }
+};
+
+GQuark
+rb_musicbrainz_error_quark (void)
+{
+	static GQuark quark = 0;
+	if (!quark)
+		quark = g_quark_from_static_string ("rb_musicbrainz_error");
+
+	return quark;
+}
+
+#define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
+GType
+rb_musicbrainz_error_get_type (void)
+{
+	static GType etype = 0;
+
+	if (etype == 0) {
+		static const GEnumValue values[] = {
+			ENUM_ENTRY (RB_MUSICBRAINZ_ERROR_NOT_FOUND, "not-found"),
+			ENUM_ENTRY (RB_MUSICBRAINZ_ERROR_SERVER, "server-error"),
+			ENUM_ENTRY (RB_MUSICBRAINZ_ERROR_NETWORK, "network-error"),
+			{ 0, 0, 0 }
+		};
+
+		etype = g_enum_register_static ("RBMusicBrainzError", values);
+	}
+
+	return etype;
+}
+
+
+static void
+free_values (GQueue *attrs)
+{
+	g_queue_free_full (attrs, (GDestroyNotify) g_free);
+}
+
+void
+rb_musicbrainz_data_free (RBMusicBrainzData *data)
+{
+	g_hash_table_unref (data->attrs);
+	g_list_free_full (data->children, (GDestroyNotify) rb_musicbrainz_data_free);
+	g_free (data->type);
+	g_free (data);
+}
+const char *
+rb_musicbrainz_data_get_attr_value (RBMusicBrainzData *data, const char *attr)
+{
+	GQueue *d;
+	d = g_hash_table_lookup (data->attrs, attr);
+	if (d == NULL) {
+		return NULL;
+	}
+
+	return d->head->data;
+}
+
+GList *
+rb_musicbrainz_data_get_attr_names (RBMusicBrainzData *data)
+{
+	return g_hash_table_get_keys (data->attrs);
+}
+
+RBMusicBrainzData *
+rb_musicbrainz_data_find_child (RBMusicBrainzData *data, const char *attr, const char *value)
+{
+	GList *l;
+	for (l = data->children; l != NULL; l = l->next) {
+		RBMusicBrainzData *child = l->data;
+		GQueue *d;
+		GList *i;
+
+		d = g_hash_table_lookup (child->attrs, attr);
+		if (d == NULL)
+			continue;
+		for (i = d->head; i != NULL; i = i->next) {
+			if (g_strcmp0 (value, i->data) == 0)
+				return child;
+		}
+	}
+
+	return NULL;
+}
+
+GList *
+rb_musicbrainz_data_get_children (RBMusicBrainzData *data)
+{
+	return g_list_copy (data->children);
+}
+
+const char *
+rb_musicbrainz_data_get_data_type (RBMusicBrainzData *data)
+{
+	return data->type;
+}
+
+static RBMusicBrainzData *
+new_data (RBMusicBrainzData *parent, const char *type)
+{
+	RBMusicBrainzData *d = g_new0 (RBMusicBrainzData, 1);
+	d->type = g_strdup (type);
+	d->parent = parent;
+	d->attrs = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)free_values);
+
+	if (parent) {
+		parent->children = g_list_append (parent->children, d);
+	}
+	return d;
+}
+
+static void
+add_attr (RBMusicBrainzData *data, const char *attr, const char *value)
+{
+	GQueue *d;
+
+	d = g_hash_table_lookup (data->attrs, attr);
+	if (d == NULL) {
+		d = g_queue_new ();
+		g_hash_table_insert (data->attrs, (char *)attr, d);
+	}
+
+	g_queue_push_tail (d, g_strdup (value));
+}
+
+static char *
+get_path (RBMusicBrainzParseContext *ctx)
+{
+	GString s = {0,};
+	GList *l;
+
+	for (l = ctx->current->path_start; l != NULL; l = l->next) {
+		g_string_append (&s, "/");
+		g_string_append (&s, l->data);
+	}
+
+	return s.str;
+}
+
+static void
+start_element (GMarkupParseContext *pctx,
+	       const gchar *element_name,
+	       const gchar **attribute_names,
+	       const gchar **attribute_values,
+	       gpointer user_data,
+	       GError **error)
+{
+	RBMusicBrainzParseContext *ctx = user_data;
+	char *path;
+	int i;
+	int j;
+
+	g_queue_push_tail (&ctx->path, g_strdup (element_name));
+
+	for (i = 0; i < G_N_ELEMENTS (object_types); i++) {
+		if (g_strcmp0 (element_name, object_types[i].name) == 0) {
+			RBMusicBrainzData *d = new_data (ctx->current, element_name);
+			d->path_start = ctx->path.tail;
+			ctx->current = d;
+			ctx->map = object_types[i].map;
+			break;
+		}
+	}
+
+	path = get_path (ctx);
+	for (i = 0; ctx->map[i].path != NULL; i++) {
+		if (g_strcmp0 (path, ctx->map[i].path) == 0) {
+			if (ctx->map[i].xml_attr != NULL) {
+				for (j = 0; attribute_names[j] != NULL; j++) {
+					if (g_strcmp0 (attribute_names[j], ctx->map[i].xml_attr) == 0) {
+						add_attr (ctx->current,
+							  (char *)ctx->map[i].attr,
+							  attribute_values[j]);
+					}
+				}
+			} else {
+				ctx->item = ctx->map[i].attr;
+			}
+			break;
+		}
+	}
+
+	g_free (path);
+}
+
+static void
+end_element (GMarkupParseContext *pctx,
+	     const gchar *element_name,
+	     gpointer user_data,
+	     GError **error)
+{
+	RBMusicBrainzParseContext *ctx = user_data;
+
+	if (ctx->item) {
+		add_attr (ctx->current, (char *)ctx->item, ctx->text.str);
+		ctx->item = NULL;
+	}
+
+	if (ctx->path.tail == ctx->current->path_start) {
+		ctx->current->path_start = NULL;
+		ctx->current = ctx->current->parent;
+	}
+
+	g_free (g_queue_pop_tail (&ctx->path));
+
+	g_free (ctx->text.str);
+	ctx->text.str = NULL;
+	ctx->text.len = 0;
+	ctx->text.allocated_len = 0;
+}
+
+static void
+text (GMarkupParseContext *pctx,
+      const gchar *text,
+      gsize text_len,
+      gpointer user_data,
+      GError **error)
+{
+	RBMusicBrainzParseContext *ctx = user_data;
+
+	if (ctx->item) {
+		g_string_append (&ctx->text, text);
+	}
+}
+
+
+RBMusicBrainzData *
+rb_musicbrainz_data_parse (const char *data, gssize len, GError **error)
+{
+	RBMusicBrainzParseContext ctx;
+	GMarkupParser parser = {
+		start_element,
+		end_element,
+		text
+	};
+	GMarkupParseContext *pctx;
+
+	ctx.root = new_data (NULL, "root");
+	ctx.current = ctx.root;
+	ctx.text.str = NULL;
+	ctx.text.len = 0;
+	ctx.text.allocated_len = 0;
+	g_queue_init (&ctx.path);
+
+	pctx = g_markup_parse_context_new (&parser, 0, &ctx, NULL);
+	if (g_markup_parse_context_parse (pctx, data, len, error) == FALSE) {
+		rb_musicbrainz_data_free (ctx.root);
+		return NULL;
+	}
+
+	if (g_markup_parse_context_end_parse (pctx, error) == FALSE) {
+		rb_musicbrainz_data_free (ctx.root);
+		return NULL;
+	}
+	g_markup_parse_context_free (pctx);
+
+	return ctx.root;
+}
+
+GList *
+rb_musicbrainz_data_get_attr_values (RBMusicBrainzData *data, const char *attr)
+{
+	GQueue *d;
+	d = g_hash_table_lookup (data->attrs, attr);
+	if (d == NULL) {
+		return NULL;
+	}
+
+	return g_list_copy (d->head);
+}
+
+
+static void
+lookup_cb (SoupSession *session, SoupMessage *msg, GSimpleAsyncResult *result)
+{
+	RBMusicBrainzData *data;
+	int code;
+	GError *error = NULL;
+
+	g_object_get (msg, SOUP_MESSAGE_STATUS_CODE, &code, NULL);
+	if (code == SOUP_STATUS_NOT_FOUND || code == SOUP_STATUS_BAD_REQUEST)  {
+		g_simple_async_result_set_error (result,
+						 RB_MUSICBRAINZ_ERROR,
+						 RB_MUSICBRAINZ_ERROR_NOT_FOUND,
+						 _("Not found"));
+	} else if (code < 100) {
+		g_simple_async_result_set_error (result,
+						 RB_MUSICBRAINZ_ERROR,
+						 RB_MUSICBRAINZ_ERROR_NETWORK,
+						 _("Unable to connect to Musicbrainz server"));
+	} else if (code != SOUP_STATUS_OK || msg->response_body->data == NULL) {
+		g_simple_async_result_set_error (result,
+						 RB_MUSICBRAINZ_ERROR,
+						 RB_MUSICBRAINZ_ERROR_SERVER,
+						 _("Musicbrainz server error"));
+	} else {
+		data = rb_musicbrainz_data_parse (msg->response_body->data,
+						  msg->response_body->length,
+						  &error);
+		if (data == NULL) {
+			g_simple_async_result_set_from_error (result, error);
+			g_clear_error (&error);
+		} else {
+			g_simple_async_result_set_op_res_gpointer (result, data, NULL);
+		}
+	}
+
+	g_simple_async_result_complete (result);
+	g_object_unref (result);
+	g_object_unref (session);
+}
+
+void
+rb_musicbrainz_lookup (const char *entity,
+		       const char *entity_id,
+		       const char **includes,
+		       GCancellable *cancellable,
+		       GAsyncReadyCallback callback,
+		       gpointer user_data)
+{
+	GSimpleAsyncResult *result;
+	SoupURI *uri;
+	SoupMessage *message;
+	SoupSession *session;
+	char *uri_str;
+	char *inc;
+
+	result = g_simple_async_result_new (NULL,
+					    callback,
+					    user_data,
+					    rb_musicbrainz_lookup);
+	g_simple_async_result_set_check_cancellable (result, cancellable);
+
+	session = soup_session_async_new_with_options (SOUP_SESSION_ADD_FEATURE_BY_TYPE,
+						       SOUP_TYPE_GNOME_FEATURES_2_26,
+						       SOUP_SESSION_USER_AGENT,
+						       "Rhythmbox/" VERSION " ",
+						       NULL);
+	uri_str = g_strdup_printf ("http://musicbrainz.org/ws/2/%s/%s";, entity, entity_id);
+	uri = soup_uri_new (uri_str);
+	g_free (uri_str);
+
+	if (includes != NULL) {
+		inc = g_strjoinv ("+", (char **)includes);
+		soup_uri_set_query_from_fields (uri, "inc", inc, NULL);
+		g_free (inc);
+	}
+
+	message = soup_message_new_from_uri (SOUP_METHOD_GET, uri);
+	soup_uri_free (uri);
+
+	soup_session_queue_message (session,
+				    message,
+				    (SoupSessionCallback) lookup_cb,
+				    result);
+}
+
+RBMusicBrainzData *
+rb_musicbrainz_lookup_finish (GAsyncResult *result,
+			      GError **error)
+{
+	g_return_val_if_fail (g_simple_async_result_is_valid (result,
+							      NULL,
+							      rb_musicbrainz_lookup),
+			      NULL);
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+
+	return g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
+}
+
+char *
+rb_musicbrainz_create_submit_url (const char *disc_id, const char *full_disc_id)
+{
+	char **bits;
+	int *intbits;
+	GString *url;
+	int i;
+	int n;
+
+	/* full disc id is a space-delimited list of hex numbers.. */
+	bits = g_strsplit (full_disc_id, " ", 0);
+	n = g_strv_length (bits);
+	intbits = g_new0 (int, n+1);
+	for (i = 0; i < n; i++) {
+		intbits[i] = strtol (bits[i], 0, 16);
+	}
+	g_strfreev (bits);
+
+	url = g_string_new ("http://mm.musicbrainz.org/bare/cdlookup.html?id=";);
+
+	g_string_append (url, disc_id);		/* urlencode? */
+	g_string_append_printf (url, "&tracks=%d&toc=%d", intbits[1], intbits[0]);
+
+	/* .. that we put in the url in decimal */
+	for (i = 1; i < n; i++) {
+		g_string_append_printf (url, "+%d", intbits[i]);
+	}
+	
+	g_free (intbits);
+
+	return g_string_free (url, FALSE);
+}
diff --git a/plugins/audiocd/rb-musicbrainz-lookup.h b/plugins/audiocd/rb-musicbrainz-lookup.h
new file mode 100644
index 0000000..c9e2677
--- /dev/null
+++ b/plugins/audiocd/rb-musicbrainz-lookup.h
@@ -0,0 +1,110 @@
+/*
+ *  Copyright (C) 2012 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_MUSICBRAINZ_LOOKUP_H
+#define RB_MUSICBRAINZ_LOOKUP_H
+
+#include <glib.h>
+#include <gio/gio.h>
+
+G_BEGIN_DECLS
+
+#define RB_MUSICBRAINZ_ATTR_ASIN			"asin"
+#define RB_MUSICBRAINZ_ATTR_COUNTRY			"country"
+#define RB_MUSICBRAINZ_ATTR_DATE			"date"
+#define RB_MUSICBRAINZ_ATTR_TITLE			"title"
+#define RB_MUSICBRAINZ_ATTR_ALBUM			"album"
+#define RB_MUSICBRAINZ_ATTR_ALBUM_ID			"album-id"
+#define RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST		"album-artist"
+#define RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST_ID		"album-artist-id"
+#define RB_MUSICBRAINZ_ATTR_ALBUM_ARTIST_SORTNAME	"album-artist-sortname"
+#define RB_MUSICBRAINZ_ATTR_DISC_ID			"disc-id"
+#define RB_MUSICBRAINZ_ATTR_DISC_NUMBER			"disc-number"
+#define RB_MUSICBRAINZ_ATTR_TRACK_COUNT			"track-count"
+#define RB_MUSICBRAINZ_ATTR_TRACK_NUMBER		"track-number"
+#define RB_MUSICBRAINZ_ATTR_DURATION			"duration"
+#define RB_MUSICBRAINZ_ATTR_TRACK_ID			"track-id"
+#define RB_MUSICBRAINZ_ATTR_TITLE			"title"
+#define RB_MUSICBRAINZ_ATTR_ARTIST			"artist"
+#define RB_MUSICBRAINZ_ATTR_ARTIST_ID			"artist-id"
+#define RB_MUSICBRAINZ_ATTR_ARTIST_SORTNAME		"artist-sortname"
+#define RB_MUSICBRAINZ_ATTR_RELATION_TYPE		"relation-type"
+#define RB_MUSICBRAINZ_ATTR_RELATION_TARGET		"relation-target"
+#define RB_MUSICBRAINZ_ATTR_WORK_ID			"work-id"
+#define RB_MUSICBRAINZ_ATTR_WORK_TITLE			"work-title"
+
+typedef enum
+{
+	RB_MUSICBRAINZ_ERROR_NOT_FOUND,
+	RB_MUSICBRAINZ_ERROR_NETWORK,
+	RB_MUSICBRAINZ_ERROR_SERVER
+} RBMusicBrainzError;
+
+GType rb_musicbrainz_error_get_type (void);
+GQuark rb_musicbrainz_error_quark (void);
+#define RB_TYPE_MUSICBRAINZ_ERROR (rb_musicbrainz_error_get_type())
+#define RB_MUSICBRAINZ_ERROR (rb_musicbrainz_error_quark())
+
+typedef struct _RBMusicBrainzData RBMusicBrainzData;
+
+void			rb_musicbrainz_data_free		(RBMusicBrainzData *data);
+const char *		rb_musicbrainz_data_get_data_type	(RBMusicBrainzData *data);
+
+GList *			rb_musicbrainz_data_get_attr_names	(RBMusicBrainzData *data);
+GList *			rb_musicbrainz_data_get_attr_values	(RBMusicBrainzData *data,
+								 const char *attr);
+const char *		rb_musicbrainz_data_get_attr_value	(RBMusicBrainzData *data,
+								 const char *attr);
+
+RBMusicBrainzData *	rb_musicbrainz_data_find_child		(RBMusicBrainzData *data,
+								 const char *attr,
+								 const char *value);
+
+GList *			rb_musicbrainz_data_get_children	(RBMusicBrainzData *data);
+
+
+RBMusicBrainzData *	rb_musicbrainz_data_parse		(const char *data,
+								 gssize len,
+								 GError **error);
+
+void			rb_musicbrainz_lookup			(const char *entity,
+								 const char *entity_id,
+								 const char **includes,
+								 GCancellable *cancellable,
+								 GAsyncReadyCallback callback,
+								 gpointer user_data);
+
+RBMusicBrainzData *	rb_musicbrainz_lookup_finish		(GAsyncResult *result,
+								 GError **error);
+
+char *			rb_musicbrainz_create_submit_url	(const char *disc_id,
+								 const char *full_disc_id);
+
+
+G_END_DECLS
+
+#endif /* RB_MUSICBRAINZ_LOOKUP_H */
diff --git a/plugins/audiocd/test-cd.c b/plugins/audiocd/test-cd.c
new file mode 100644
index 0000000..38c84c3
--- /dev/null
+++ b/plugins/audiocd/test-cd.c
@@ -0,0 +1,234 @@
+/* -*- 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 <gst/gst.h>
+
+#include "rb-audiocd-info.h"
+#include "rb-musicbrainz-lookup.h"
+
+GMainLoop *mainloop = NULL;
+
+static void
+indent (int d)
+{
+	int i;
+	for (i = 0; i < d; i++)
+		g_print("  ");
+}
+
+static void
+dump (RBMusicBrainzData *d, int depth, int recurse)
+{
+	GList *l, *i;
+
+	indent (depth);
+	g_print ("%s {\n", rb_musicbrainz_data_get_data_type (d));
+	l = rb_musicbrainz_data_get_attr_names (d);
+	for (i = l; i != NULL; i = i->next) {
+		GList *v, *vi;
+
+		indent (depth+1);
+		g_print ("%s = [", (char *)i->data);
+		v = rb_musicbrainz_data_get_attr_values (d, i->data);
+		for (vi = v; vi != NULL; vi = vi->next) {
+			if (vi != v)
+				g_print (", ");
+			g_print ("%s", (char *)vi->data);
+		}
+		g_list_free (v);
+		g_print ("]\n");
+	}
+	g_list_free (l);
+
+	if (recurse) {
+		l = rb_musicbrainz_data_get_children (d);
+		for (i = l; i != NULL; i = i->next) {
+			dump (i->data, depth+1, recurse);
+		}
+		g_list_free (l);
+	}
+
+	indent (depth);
+	g_print ("}\n");
+}
+
+static void
+release_lookup_cb (GObject *object, GAsyncResult *result, RBAudioCDInfo *info)
+{
+	RBMusicBrainzData *dd;
+	GError *error = NULL;
+	dd = rb_musicbrainz_lookup_finish (result, &error);
+	if (dd != NULL) {
+		RBMusicBrainzData *r;
+		RBMusicBrainzData *m;
+
+		r = rb_musicbrainz_data_get_children (dd)->data;
+		m = rb_musicbrainz_data_find_child (r, "disc-id", info->musicbrainz_disc_id);
+		dump (r, 0, 0);
+		dump (m, 0, 1);
+		rb_musicbrainz_data_free (dd);
+	}
+
+	g_main_loop_quit (mainloop);
+	rb_audiocd_info_free (info);
+}
+
+static void
+lookup_cb (GObject *object, GAsyncResult *result, RBAudioCDInfo *info)
+{
+	RBMusicBrainzData *dd;
+	GError *error = NULL;
+	GList *l;
+	const char *release_includes[] = {
+		"discids",
+		"media",
+		"recordings",
+		"artist-credits",
+		"work-rels",
+		"recording-level-rels",
+		"work-level-rels",
+		"artist-rels",
+		"url-rels",
+		NULL
+	};
+
+	dd = rb_musicbrainz_lookup_finish (result, &error);
+	if (dd != NULL) {
+		g_print ("\n\n");
+		for (l = rb_musicbrainz_data_get_children (dd); l != NULL; l = l->next) {
+			RBMusicBrainzData *r = l->data;
+
+			RBMusicBrainzData *m = rb_musicbrainz_data_find_child (r, "disc-id", info->musicbrainz_disc_id);
+			if (m != NULL) {
+				dump (r, 0, 0);
+				dump (m, 0, 1);
+
+				rb_musicbrainz_lookup ("release",
+						       rb_musicbrainz_data_get_attr_value (r, RB_MUSICBRAINZ_ATTR_ALBUM_ID),
+						       release_includes,
+						       NULL,
+						       (GAsyncReadyCallback) release_lookup_cb,
+						       info);
+				break;
+			}
+		}
+		rb_musicbrainz_data_free (dd);
+	} else {
+		g_print ("lookup failed: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+}
+
+
+static void
+audiocd_info_cb (GObject *source, GAsyncResult *result, gpointer data)
+{
+	RBAudioCDInfo *info;
+	GError *error = NULL;
+	int i;
+
+	info = rb_audiocd_info_finish (result, &error);
+
+	if (error != NULL) {
+		g_print ("err: %s\n", error->message);
+		g_clear_error (&error);
+	} else {
+		g_print ("disc id: %s\n", info->musicbrainz_disc_id);
+		g_print ("%d tracks\n", info->num_tracks);
+
+		for (i = 0; i < info->num_tracks; i++) {
+			g_print ("%d: %ds\n", info->tracks[i].track_num, info->tracks[i].duration/1000);
+		}
+	}
+
+	if (info->musicbrainz_disc_id) {
+		const char *includes[] = { "artist-credits" };
+
+		rb_musicbrainz_lookup ("discid",
+				       info->musicbrainz_disc_id,
+				       includes,
+				       NULL,
+				       (GAsyncReadyCallback) lookup_cb,
+				       info);
+	} else {
+		rb_audiocd_info_free (info);
+		g_main_loop_quit (mainloop);
+	}
+}
+
+static void
+release_cb (GObject *source, GAsyncResult *result, gpointer data)
+{
+	RBMusicBrainzData *dd;
+	GError *error = NULL;
+
+	dd = rb_musicbrainz_lookup_finish (result, &error);
+	if (dd != NULL) {
+		dump (dd, 0, 1);
+		rb_musicbrainz_data_free (dd);
+	} else {
+		g_print ("lookup failed: %s\n", error->message);
+		g_clear_error (&error);
+	}
+
+	g_main_loop_quit (mainloop);
+}
+
+static gboolean
+go (const char *thing)
+{
+	if (thing[0] == '/') {
+		rb_audiocd_info_get (thing, NULL, (GAsyncReadyCallback)audiocd_info_cb, NULL);
+	} else {
+		const char *includes[] = { "artist-credits", NULL };
+		rb_musicbrainz_lookup ("release", thing, includes, NULL, (GAsyncReadyCallback) release_cb, NULL);
+	}
+	return FALSE;
+}
+
+int
+main (int argc, char **argv)
+{
+	char *thing;
+
+	g_type_init ();
+	gst_init (NULL, NULL);
+
+	if (argv[1] == NULL) {
+		thing = "/dev/cdrom";
+	} else {
+		thing = argv[1];
+	}
+
+	mainloop = g_main_loop_new (NULL, FALSE);
+	g_idle_add ((GSourceFunc) go, thing);
+	g_main_loop_run (mainloop);
+
+	gst_deinit ();
+
+	return 0;
+}
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 01d7638..dbd8d4e 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -33,14 +33,10 @@ plugins/artsearch/artsearch.py
 plugins/artsearch/lastfm.py
 [type: gettext/glade]plugins/audiocd/album-info.ui
 [type: gettext/ini]plugins/audiocd/audiocd.plugin.in
-[type: gettext/glade]plugins/audiocd/multiple-album.ui
+plugins/audiocd/rb-audiocd-info.c
 plugins/audiocd/rb-audiocd-plugin.c
 plugins/audiocd/rb-audiocd-source.c
-plugins/audiocd/sj-error.c
-plugins/audiocd/sj-metadata.c
-plugins/audiocd/sj-metadata-getter.c
-plugins/audiocd/sj-metadata-gvfs.c
-plugins/audiocd/sj-structures.c
+plugins/audiocd/rb-musicbrainz-lookup.c
 [type: gettext/ini]plugins/audioscrobbler/audioscrobbler.plugin.in
 [type: gettext/glade]plugins/audioscrobbler/audioscrobbler-preferences.ui
 [type: gettext/glade]plugins/audioscrobbler/audioscrobbler-profile.ui



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