[easytag/wip/musicbrainz-dialog] Add simple asynchronous query support
- From: David King <davidk src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [easytag/wip/musicbrainz-dialog] Add simple asynchronous query support
- Date: Wed, 26 Nov 2014 21:52:54 +0000 (UTC)
commit 52afca8e0bc167f6a4fa52f6c07c77c3ac6cab7c
Author: David King <amigadave amigadave com>
Date: Tue Nov 25 00:17:32 2014 +0000
Add simple asynchronous query support
Use GSimpleAsyncResult to dispatch MusicBrainz queries from a thread, in
order to keep the UI responsive.
data/musicbrainz_dialog.ui | 17 +++
src/musicbrainz.c | 321 ++++++++++++++++++++++++++++++++++++++++++++
src/musicbrainz.h | 29 ++++-
src/musicbrainz_dialog.c | 158 ++++++++++++++++++++++
4 files changed, 524 insertions(+), 1 deletions(-)
---
diff --git a/data/musicbrainz_dialog.ui b/data/musicbrainz_dialog.ui
index f029b5a..14c02d2 100644
--- a/data/musicbrainz_dialog.ui
+++ b/data/musicbrainz_dialog.ui
@@ -1,5 +1,10 @@
<interface domain="easytag">
<requires lib="gtk+" version="3.4"/>
+ <object class="GtkListStore" id="results_model">
+ <columns>
+ <column type="gchararray"/>
+ </columns>
+ </object>
<object class="GtkGrid" id="musicbrainz_grid">
<property name="column-spacing">6</property>
<property name="row-spacing">6</property>
@@ -96,7 +101,19 @@
<property name="visible">True</property>
<child>
<object class="GtkTreeView" id="results_view">
+ <property name="model">results_model</property>
<property name="visible">True</property>
+ <child>
+ <object class="GtkTreeViewColumn" id="name_column">
+ <property name="title" translatable="yes">Name</property>
+ <child>
+ <object class="GtkCellRendererText" id="name_renderer"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
</object>
</child>
</object>
diff --git a/src/musicbrainz.c b/src/musicbrainz.c
index 133c841..254d949 100644
--- a/src/musicbrainz.c
+++ b/src/musicbrainz.c
@@ -30,11 +30,261 @@ G_DEFINE_TYPE (EtMusicbrainz, et_musicbrainz, G_TYPE_OBJECT)
#define et_musicbrainz_get_instance_private(musicbrainz) (musicbrainz->priv)
+#define GET(field, function, obj) \
+{ \
+ function (obj, buffer, sizeof (buffer)); \
+ \
+ if (field) \
+ { \
+ g_free (field); \
+ } \
+ \
+ if (*buffer == '\0') \
+ { \
+ field = NULL; \
+ } \
+ else \
+ { \
+ field = g_strdup (buffer); \
+ } \
+}
+
struct _EtMusicbrainzPrivate
{
Mb5Query query;
};
+struct _EtMusicbrainzQuery
+{
+ /*< private >*/
+ EtMusicbrainzEntity entity;
+ gchar *search_string;
+ gint ref_count;
+};
+
+struct _EtMusicbrainzResult
+{
+ /*< private >*/
+ GList *list;
+ gint ref_count;
+};
+
+EtMusicbrainzQuery *
+et_musicbrainz_query_new (EtMusicbrainzEntity entity,
+ const gchar *search_string)
+{
+ EtMusicbrainzQuery *query;
+
+ query = g_slice_new (EtMusicbrainzQuery);
+ query->entity = entity;
+ query->ref_count = 1;
+ query->search_string = g_strdup (search_string);
+
+ return query;
+}
+
+EtMusicbrainzQuery *
+et_musicbrainz_query_ref (EtMusicbrainzQuery *query)
+{
+ g_return_val_if_fail (query != NULL, NULL);
+
+ g_atomic_int_inc (&query->ref_count);
+
+ return query;
+}
+
+void
+et_musicbrainz_query_unref (EtMusicbrainzQuery *query)
+{
+ g_return_if_fail (query != NULL);
+
+ if (g_atomic_int_dec_and_test (&query->ref_count))
+ {
+ g_free (query->search_string);
+ g_slice_free (EtMusicbrainzQuery, query);
+ }
+}
+
+static EtMusicbrainzResult *
+et_musicbrainz_result_new (EtMusicbrainzEntity entity,
+ Mb5Metadata metadata)
+{
+ EtMusicbrainzResult *result;
+ GList *list = NULL;
+ gchar buffer[512]; /* For the GET macro. */
+
+ result = g_slice_new (EtMusicbrainzResult);
+ result->ref_count = 1;
+
+ switch (entity)
+ {
+ case ET_MUSICBRAINZ_ENTITY_ARTIST:
+ {
+ Mb5ArtistList artist_list;
+ gsize i;
+ gint n_artists;
+
+ artist_list = mb5_metadata_get_artistlist (metadata);
+ n_artists = mb5_artist_list_size (artist_list);
+
+ for (i = 0; i < n_artists; i++)
+ {
+ Mb5Artist artist;
+ gchar *name = NULL;
+
+ /* TODO: Come up with a full object for describing an
+ * artist. */
+ artist = mb5_artist_list_item (artist_list, i);
+ GET (name, mb5_artist_get_name, artist);
+ list = g_list_prepend (list, name);
+ }
+ }
+ break;
+ case ET_MUSICBRAINZ_ENTITY_RELEASE_GROUP:
+ {
+ Mb5ReleaseGroupList release_group_list;
+ gsize i;
+ gint n_release_groups;
+
+ release_group_list = mb5_metadata_get_releasegrouplist (metadata);
+ n_release_groups = mb5_releasegroup_list_size (release_group_list);
+
+ for (i = 0; i < n_release_groups; i++)
+ {
+ Mb5ReleaseGroup release_group;
+ gchar *name = NULL;
+
+ /* TODO: Come up with a full object for describing a release
+ * group. */
+ release_group = mb5_releasegroup_list_item (release_group_list,
+ i);
+ GET (name, mb5_releasegroup_get_title, release_group);
+ list = g_list_prepend (list, name);
+ }
+ }
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ result->list = g_list_reverse (list);
+
+ return result;
+}
+
+EtMusicbrainzResult *
+et_musicbrainz_result_ref (EtMusicbrainzResult *result)
+{
+ g_return_val_if_fail (result != NULL, NULL);
+
+ g_atomic_int_inc (&result->ref_count);
+
+ return result;
+}
+
+void
+et_musicbrainz_result_unref (EtMusicbrainzResult *result)
+{
+ g_return_if_fail (result != NULL);
+
+ if (g_atomic_int_dec_and_test (&result->ref_count))
+ {
+ g_list_free_full (result->list, g_free);
+ g_slice_free (EtMusicbrainzResult, result);
+ }
+}
+
+GList *
+et_musicbrainz_result_get_results (EtMusicbrainzResult *result)
+{
+ g_return_val_if_fail (result != NULL, NULL);
+
+ return result->list;
+}
+
+static EtMusicbrainzResult *
+query_mb_from_et_query (Mb5Query mb_query,
+ EtMusicbrainzQuery *query)
+{
+ const gchar *entity;
+ gchar *param_names[1] = { "query" };
+ gchar *param_values[1];
+ Mb5Metadata metadata;
+ EtMusicbrainzResult *result = NULL;
+
+ switch (query->entity)
+ {
+ case ET_MUSICBRAINZ_ENTITY_ARTIST:
+ entity = "artist";
+ param_values[0] = g_strconcat ("artist:",
+ query->search_string, NULL);
+ break;
+ case ET_MUSICBRAINZ_ENTITY_RELEASE_GROUP:
+ entity = "release-group";
+ param_values[0] = g_strconcat ("releasegroup:",
+ query->search_string, NULL);
+ break;
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+
+ metadata = mb5_query_query (mb_query, entity, "", "", 1, param_names,
+ param_values);
+
+ g_free (param_values[0]);
+
+ if (metadata != NULL)
+ {
+ result = et_musicbrainz_result_new (query->entity, metadata);
+ mb5_metadata_delete (metadata);
+ }
+
+ return result;
+}
+
+static void
+search_thread_func (GSimpleAsyncResult *result,
+ GObject *musicbrainz,
+ GCancellable *cancellable)
+{
+ EtMusicbrainz *self;
+ EtMusicbrainzPrivate *priv;
+ EtMusicbrainzQuery *query;
+ EtMusicbrainzResult *mb_result;
+
+ self = ET_MUSICBRAINZ (musicbrainz);
+ priv = et_musicbrainz_get_instance_private (self);
+
+ query = g_simple_async_result_get_op_res_gpointer (result);
+
+ mb_result = query_mb_from_et_query (priv->query, query);
+
+ if (mb_result == NULL)
+ {
+ gint code;
+ gint len;
+ gchar *message;
+
+ code = mb5_query_get_lasthttpcode (priv->query);
+ len = mb5_query_get_lasterrormessage (priv->query, NULL, 0);
+ message = g_malloc (len);
+ mb5_query_get_lasterrormessage (priv->query, message, len);
+
+ /* TODO: Use a translatable string? */
+ g_simple_async_result_set_error (result, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "MusicBrainz search query failed with error code '%d': %s",
+ code, message);
+ g_free (message);
+ }
+ else
+ {
+ g_simple_async_result_set_op_res_gpointer (result, mb_result,
+ (GDestroyNotify)et_musicbrainz_result_unref);
+ }
+}
+
static void
et_musicbrainz_finalize (GObject *object)
{
@@ -90,4 +340,75 @@ et_musicbrainz_new (void)
return g_object_new (ET_TYPE_MUSICBRAINZ, NULL);
}
+/*
+ * et_musicbrainz_search_async:
+ * @self: the musicbrainz context from which to initiate the search
+ * @query: parameters for the search query
+ * @cancellable: a cancellable, or %NULL
+ * @callback: a callback to trigger when the call is complete
+ * @user_data: user data to be passed to @callback
+ *
+ * Start an asynchronous search, submitting a query according to @info and
+ * delivering the result to @callback when complete.
+ */
+void
+et_musicbrainz_search_async (EtMusicbrainz *self,
+ EtMusicbrainzQuery *query,
+ GCancellable *cancellable,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ g_return_if_fail (ET_MUSICBRAINZ (self));
+ g_return_if_fail (query != NULL && callback != NULL);
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data,
+ et_musicbrainz_search_async);
+
+ g_simple_async_result_set_op_res_gpointer (result,
+ et_musicbrainz_query_ref (query),
+ (GDestroyNotify)et_musicbrainz_query_unref);
+ g_simple_async_result_run_in_thread (result, search_thread_func,
+ G_PRIORITY_DEFAULT, cancellable);
+ g_object_unref (result);
+}
+
+/*
+ * et_musicbrainz_search_finish:
+ * @self: the musicbrainz context on which the search was initiated
+ * @result: the result obtained in the ready callback from
+ * et_musicbrainz_search_async()
+ * @error: an error, or %NULL
+ *
+ * Finishes an asynchronous operation started with
+ * et_musicbrainz_search_async().
+ *
+ * Returns: a result, or %NULL on error
+ */
+EtMusicbrainzResult *
+et_musicbrainz_search_finish (EtMusicbrainz *self,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ EtMusicbrainzResult *mb_result;
+
+ g_return_val_if_fail (g_simple_async_result_is_valid (result,
+ G_OBJECT (self),
+ et_musicbrainz_search_async),
+ NULL);
+
+ simple = (GSimpleAsyncResult *)result;
+
+ if (g_simple_async_result_propagate_error (simple, error))
+ {
+ return NULL;
+ }
+
+ mb_result = (EtMusicbrainzResult *)g_simple_async_result_get_op_res_gpointer (simple);
+
+ return et_musicbrainz_result_ref (mb_result);
+}
+
#endif /* ENABLE_MUSICBRAINZ */
diff --git a/src/musicbrainz.h b/src/musicbrainz.h
index 4cab216..897e6d6 100644
--- a/src/musicbrainz.h
+++ b/src/musicbrainz.h
@@ -23,7 +23,7 @@
#ifdef ENABLE_MUSICBRAINZ
-#include <glib-object.h>
+#include <gio/gio.h>
G_BEGIN_DECLS
@@ -34,6 +34,22 @@ typedef struct _EtMusicbrainz EtMusicbrainz;
typedef struct _EtMusicbrainzClass EtMusicbrainzClass;
typedef struct _EtMusicbrainzPrivate EtMusicbrainzPrivate;
+/*
+ * EtMusicbrainzEntity:
+ * @ET_MUSICBRAINZ_ENTITY_RELEASE_GROUP:
+ * @ET_MUSICBRAINZ_ENTITY_ARTIST:
+ *
+ * Entity type to query against the MusicBrainz web service.
+ */
+typedef enum
+{
+ ET_MUSICBRAINZ_ENTITY_RELEASE_GROUP,
+ ET_MUSICBRAINZ_ENTITY_ARTIST
+} EtMusicbrainzEntity;
+
+typedef struct _EtMusicbrainzQuery EtMusicbrainzQuery;
+typedef struct _EtMusicbrainzResult EtMusicbrainzResult;
+
struct _EtMusicbrainz
{
/*< private >*/
@@ -50,6 +66,17 @@ struct _EtMusicbrainzClass
GType et_musicbrainz_get_type (void);
EtMusicbrainz *et_musicbrainz_new (void);
+void et_musicbrainz_search_async (EtMusicbrainz *self, EtMusicbrainzQuery *query, GCancellable *cancellable,
GAsyncReadyCallback callback, gpointer user_data);
+EtMusicbrainzResult * et_musicbrainz_search_finish (EtMusicbrainz *self, GAsyncResult *result, GError
**error);
+
+EtMusicbrainzQuery * et_musicbrainz_query_new (EtMusicbrainzEntity entity, const gchar *search_string);
+EtMusicbrainzQuery * et_musicbrainz_query_ref (EtMusicbrainzQuery *query);
+void et_musicbrainz_query_unref (EtMusicbrainzQuery *query);
+
+EtMusicbrainzResult * et_musicbrainz_result_ref (EtMusicbrainzResult *result);
+void et_musicbrainz_result_unref (EtMusicbrainzResult *result);
+GList * et_musicbrainz_result_get_results (EtMusicbrainzResult *result);
+
G_END_DECLS
#endif /* ENABLE_MUSICBRAINZ */
diff --git a/src/musicbrainz_dialog.c b/src/musicbrainz_dialog.c
index 5fe7144..166c478 100644
--- a/src/musicbrainz_dialog.c
+++ b/src/musicbrainz_dialog.c
@@ -37,9 +37,138 @@ static guint BOX_SPACING = 6;
struct _EtMusicbrainzDialogPrivate
{
EtMusicbrainz *mb;
+
+ GtkWidget *search_combo;
+ GtkWidget *search_entry;
+ GtkWidget *search_button;
+ GtkWidget *stop_button;
+ GtkWidget *results_view;
+
+ GtkListStore *results_model;
+
+ GCancellable *cancellable;
};
static void
+stop_search (EtMusicbrainzDialog *self)
+{
+ EtMusicbrainzDialogPrivate *priv;
+
+ priv = et_musicbrainz_dialog_get_instance_private (self);
+
+ gtk_widget_set_sensitive (priv->search_combo, TRUE);
+ gtk_widget_set_sensitive (priv->search_entry, TRUE);
+ gtk_widget_set_sensitive (priv->search_button, TRUE);
+ gtk_widget_set_sensitive (priv->stop_button, FALSE);
+ gtk_widget_set_sensitive (priv->results_view, TRUE);
+}
+
+static void
+on_stop_button_clicked (EtMusicbrainzDialog *self,
+ GtkButton *stop_button)
+{
+ EtMusicbrainzDialogPrivate *priv;
+
+ priv = et_musicbrainz_dialog_get_instance_private (self);
+
+ g_cancellable_cancel (priv->cancellable);
+ g_object_unref (priv->cancellable);
+ priv->cancellable = g_cancellable_new ();
+
+ stop_search (self);
+}
+
+static void
+add_string_to_results_model (const gchar *string,
+ GtkListStore *model)
+{
+ gtk_list_store_insert_with_values (model, NULL, -1, 0, string, -1);
+}
+
+static void
+query_complete_cb (GObject *source_object,
+ GAsyncResult *res,
+ gpointer user_data)
+{
+ EtMusicbrainzDialog *self;
+ EtMusicbrainzDialogPrivate *priv;
+ EtMusicbrainzResult *result;
+ GError *error = NULL;
+ GList *results;
+
+ self = ET_MUSICBRAINZ_DIALOG (user_data);
+ priv = et_musicbrainz_dialog_get_instance_private (self);
+
+ result = et_musicbrainz_search_finish (priv->mb, res, &error);
+
+ if (!result)
+ {
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ {
+ g_debug ("%s", "MusicBrainz search cancelled by user");
+ }
+ else
+ {
+ /* TODO: Show the error in the UI. */
+ g_message ("Search failed: %s", error->message);
+ }
+
+ g_error_free (error);
+ return;
+ }
+
+ results = et_musicbrainz_result_get_results (result);
+
+ g_list_foreach (results, (GFunc)add_string_to_results_model,
+ priv->results_model);
+
+ et_musicbrainz_result_unref (result);
+
+ stop_search (self);
+}
+
+static void
+start_search (EtMusicbrainzDialog *self)
+{
+ EtMusicbrainzDialogPrivate *priv;
+ EtMusicbrainzEntity entity;
+ EtMusicbrainzQuery *query;
+
+ priv = et_musicbrainz_dialog_get_instance_private (self);
+
+ gtk_widget_set_sensitive (priv->search_combo, FALSE);
+ gtk_widget_set_sensitive (priv->search_entry, FALSE);
+ gtk_widget_set_sensitive (priv->search_button, FALSE);
+ gtk_widget_set_sensitive (priv->stop_button, TRUE);
+ gtk_widget_set_sensitive (priv->results_view, FALSE);
+
+ gtk_list_store_clear (priv->results_model);
+
+ entity = gtk_combo_box_get_active (GTK_COMBO_BOX (priv->search_combo));
+ query = et_musicbrainz_query_new (entity,
+ gtk_entry_get_text (GTK_ENTRY (priv->search_entry)));
+
+ et_musicbrainz_search_async (priv->mb, query, priv->cancellable,
+ query_complete_cb, self);
+
+ et_musicbrainz_query_unref (query);
+}
+
+static void
+on_search_button_clicked (EtMusicbrainzDialog *self,
+ GtkButton *search_button)
+{
+ start_search (self);
+}
+
+static void
+on_search_entry_activate (EtMusicbrainzDialog *self,
+ GtkEntry *search_entry)
+{
+ start_search (self);
+}
+
+static void
create_musicbrainz_dialog (EtMusicbrainzDialog *self)
{
EtMusicbrainzDialogPrivate *priv;
@@ -71,6 +200,28 @@ create_musicbrainz_dialog (EtMusicbrainzDialog *self)
error->message);
}
+ priv->search_combo = GTK_WIDGET (gtk_builder_get_object (builder,
+ "search_combo"));
+ priv->search_entry = GTK_WIDGET (gtk_builder_get_object (builder,
+ "search_entry"));
+ g_signal_connect_swapped (priv->search_entry, "activate",
+ G_CALLBACK (on_search_entry_activate), self);
+
+ priv->search_button = GTK_WIDGET (gtk_builder_get_object (builder,
+ "search_button"));
+ g_signal_connect_swapped (priv->search_button, "clicked",
+ G_CALLBACK (on_search_button_clicked), self);
+
+ priv->stop_button = GTK_WIDGET (gtk_builder_get_object (builder,
+ "stop_button"));
+ g_signal_connect_swapped (priv->stop_button, "clicked",
+ G_CALLBACK (on_stop_button_clicked), self);
+
+ priv->results_model = GTK_LIST_STORE (gtk_builder_get_object (builder,
+ "results_model"));
+ priv->results_view = GTK_WIDGET (gtk_builder_get_object (builder,
+ "results_view"));
+
grid = GTK_WIDGET (gtk_builder_get_object (builder, "musicbrainz_grid"));
gtk_container_add (GTK_CONTAINER (content_area), grid);
@@ -88,6 +239,12 @@ et_musicbrainz_dialog_finalize (GObject *object)
g_clear_object (&priv->mb);
+ if (priv->cancellable)
+ {
+ g_cancellable_cancel (priv->cancellable);
+ g_clear_object (&priv->cancellable);
+ }
+
G_OBJECT_CLASS (et_musicbrainz_dialog_parent_class)->finalize (object);
}
@@ -101,6 +258,7 @@ et_musicbrainz_dialog_init (EtMusicbrainzDialog *self)
EtMusicbrainzDialogPrivate);
priv->mb = et_musicbrainz_new ();
+ priv->cancellable = g_cancellable_new ();
create_musicbrainz_dialog (self);
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]