[grilo-plugins] youtube: Handle GRL_METADATATA_KEY_URL resolution asynchronously.
- From: Iago Toral Quiroga <itoral src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [grilo-plugins] youtube: Handle GRL_METADATATA_KEY_URL resolution asynchronously.
- Date: Wed, 11 Aug 2010 13:50:37 +0000 (UTC)
commit 865cebf9dca2e32ade309871c0a23d1078627fe2
Author: Iago Toral Quiroga <itoral igalia com>
Date: Wed Aug 11 15:46:24 2010 +0200
youtube: Handle GRL_METADATATA_KEY_URL resolution asynchronously.
src/youtube/grl-youtube.c | 264 ++++++++++++++++++++++++++++++++-------------
1 files changed, 189 insertions(+), 75 deletions(-)
---
diff --git a/src/youtube/grl-youtube.c b/src/youtube/grl-youtube.c
index 4215f0c..209fac1 100644
--- a/src/youtube/grl-youtube.c
+++ b/src/youtube/grl-youtube.c
@@ -110,6 +110,8 @@
typedef void (*AsyncReadCbFunc) (gchar *data, gpointer user_data);
+typedef void (*BuildMediaFromEntryCbFunc) (GrlMedia *media, gpointer user_data);
+
typedef struct {
gchar *id;
gchar *name;
@@ -128,6 +130,9 @@ typedef struct {
gpointer user_data;
guint error_code;
CategoryInfo *category_info;
+ guint emitted;
+ guint matches;
+ guint ref_count;
} OperationSpec;
typedef struct {
@@ -141,6 +146,12 @@ typedef struct {
gpointer user_data;
} AsyncReadCb;
+typedef struct {
+ GrlMedia *media;
+ BuildMediaFromEntryCbFunc callback;
+ gpointer user_data;
+} SetMediaUrlAsyncReadCb;
+
typedef enum {
YOUTUBE_MEDIA_TYPE_ROOT,
YOUTUBE_MEDIA_TYPE_FEEDS,
@@ -308,35 +319,33 @@ G_DEFINE_TYPE (GrlYoutubeSource, grl_youtube_source, GRL_TYPE_MEDIA_SOURCE);
/* ======================= Utilities ==================== */
-static void
-free_operation_spec (OperationSpec *os)
+static OperationSpec *
+operation_spec_new ()
{
- g_slice_free (OperationSpec, os);
+ g_debug ("Allocating new spec");
+ OperationSpec *os = g_slice_new0 (OperationSpec);
+ os->ref_count = 1;
+ return os;
}
-static gchar *
-read_url (const gchar *url)
+static void
+operation_spec_unref (OperationSpec *os)
{
- GVfs *vfs;
- GFile *uri;
- GError *vfs_error = NULL;
- gchar *content = NULL;
-
- vfs = g_vfs_get_default ();
-
- g_debug ("Opening '%s'", url);
- uri = g_vfs_get_file_for_uri (vfs, url);
- g_file_load_contents (uri, NULL, &content, NULL, NULL, &vfs_error);
- g_object_unref (uri);
- if (vfs_error) {
- g_warning ("Failed reading '%s': %s", url, vfs_error->message);
- return NULL;
- } else {
- return content;
+ os->ref_count--;
+ if (os->ref_count == 0) {
+ g_slice_free (OperationSpec, os);
+ g_debug ("freeing spec");
}
}
static void
+operation_spec_ref (OperationSpec *os)
+{
+ g_debug ("Reffing spec");
+ os->ref_count++;
+}
+
+static void
read_done_cb (GObject *source_object,
GAsyncResult *res,
gpointer user_data)
@@ -354,6 +363,7 @@ read_done_cb (GObject *source_object,
g_object_unref (source_object);
if (vfs_error) {
g_warning ("Failed to open '%s': %s", arc->url, vfs_error->message);
+ arc->callback (NULL, arc->user_data);
} else {
arc->callback (content, arc->user_data);
}
@@ -366,43 +376,27 @@ read_url_async (const gchar *url,
AsyncReadCbFunc callback,
gpointer user_data)
{
- GVfs *vfs;
GFile *uri;
AsyncReadCb *arc;
- vfs = g_vfs_get_default ();
-
g_debug ("Opening async '%s'", url);
arc = g_slice_new0 (AsyncReadCb);
arc->url = g_strdup (url);
arc->callback = callback;
arc->user_data = user_data;
- uri = g_vfs_get_file_for_uri (vfs, url);
+ uri = g_file_new_for_uri (url);
g_file_load_contents_async (uri, NULL, read_done_cb, arc);
}
-static gchar *
-get_video_url (const gchar *id)
+static void
+set_media_url_async_read_cb (gchar *data, gpointer user_data)
{
- gchar *video_info_url;
- gchar *data;
- GMatchInfo *match_info;
+ SetMediaUrlAsyncReadCb *cb_data = (SetMediaUrlAsyncReadCb *) user_data;
gchar *url = NULL;
+ GMatchInfo *match_info = NULL;
static GRegex *regex = NULL;
- /* The procedure to get the video url is:
- * 1) Get the video info URL using the video id
- * 2) In the video info page, there should be an array of supported formats
- * and their corresponding URLs, right now we just use the first one we get.
- * TODO: we should be able to provide various urls or at least
- * select preferred formats via configuration
- * 3) As a workaround in case no format array is found we get the video token
- * and figure out the url of the video using the video id and the token.
- */
-
- video_info_url = g_strdup_printf (YOUTUBE_VIDEO_INFO_URL, id);
- data = read_url (video_info_url);
if (!data) {
goto done;
}
@@ -438,6 +432,8 @@ get_video_url (const gchar *id)
gchar *token_start;
gchar *token_end;
gchar *token;
+ const gchar *video_id;
+
token_start = g_strrstr (data, "&token=");
if (!token_start) {
goto done;
@@ -446,25 +442,71 @@ get_video_url (const gchar *id)
token_end = strstr (token_start, "&");
token = g_strndup (token_start, token_end - token_start);
- url = g_strdup_printf (YOUTUBE_VIDEO_URL, id, token);
+ video_id = grl_media_get_id (cb_data->media);
+ url = g_strdup_printf (YOUTUBE_VIDEO_URL, video_id, token);
g_free (token);
}
done:
- g_free (video_info_url);
g_free (data);
- return url;
+ if (url) {
+ grl_media_set_url (cb_data->media, url);
+ g_free (url);
+ }
+
+ cb_data->callback (cb_data->media, cb_data->user_data);
+
+ g_free (cb_data);
}
-static GrlMedia *
+static void
+set_media_url (GrlMedia *media,
+ BuildMediaFromEntryCbFunc callback,
+ gpointer user_data)
+{
+ const gchar *video_id;
+ gchar *video_info_url;
+ SetMediaUrlAsyncReadCb *set_media_url_async_read_data;
+
+ /* The procedure to get the video url is:
+ * 1) Read the video info URL using the video id (async operation)
+ * 2) In the video info page, there should be an array of supported formats
+ * and their corresponding URLs, right now we just use the first one we get.
+ * (see set_media_url_async_read_cb).
+ * TODO: we should be able to provide various urls or at least
+ * select preferred formats via configuration
+ * TODO: we should set mime-type accordingly to the format selected
+ * 3) As a workaround in case no format array is found we get the video token
+ * and figure out the url of the video using the video id and the token.
+ */
+
+ video_id = grl_media_get_id (media);
+ video_info_url = g_strdup_printf (YOUTUBE_VIDEO_INFO_URL, video_id);
+
+ set_media_url_async_read_data = g_new0 (SetMediaUrlAsyncReadCb, 1);
+ set_media_url_async_read_data->media = media;
+ set_media_url_async_read_data->callback = callback;
+ set_media_url_async_read_data->user_data = user_data;
+
+ read_url_async (video_info_url,
+ set_media_url_async_read_cb,
+ set_media_url_async_read_data);
+
+ g_free (video_info_url);
+}
+
+static void
build_media_from_entry (GrlMedia *content,
GDataEntry *entry,
- const GList *keys)
+ const GList *keys,
+ BuildMediaFromEntryCbFunc callback,
+ gpointer user_data)
{
GDataYouTubeVideo *video;
GrlMedia *media;
GList *iter;
+ gboolean need_url = FALSE;
if (!content) {
media = grl_media_video_new ();
@@ -516,11 +558,8 @@ build_media_from_entry (GrlMedia *content,
gdata_youtube_video_get_rating (video, NULL, NULL, NULL, &average);
grl_media_set_rating (media, average, 5.00);
} else if (iter->data == GRL_METADATA_KEY_URL) {
- gchar *url = get_video_url (gdata_youtube_video_get_video_id (video));
- if (url) {
- grl_media_set_url (media, url);
- g_free (url);
- }
+ /* This needs another query and will be resolved asynchronously p Q*/
+ need_url = TRUE;
} else if (iter->data == GRL_METADATA_KEY_EXTERNAL_PLAYER) {
GDataYouTubeContent *youtube_content;
youtube_content =
@@ -535,7 +574,12 @@ build_media_from_entry (GrlMedia *content,
iter = g_list_next (iter);
}
- return media;
+ if (need_url) {
+ /* URL resolution is async */
+ set_media_url (media, callback, user_data);
+ } else {
+ callback (media, user_data);
+ }
}
static void
@@ -741,15 +785,40 @@ compute_feed_counts (GDataService *service)
}
static void
-process_entry (GDataEntry *entry, guint count, OperationSpec *os)
+build_media_from_entry_metadata_cb (GrlMedia *media, gpointer user_data)
{
- GrlMedia *media = build_media_from_entry (NULL, entry, os->keys);
- os->callback (os->source,
- os->operation_id,
- media,
- count,
- os->user_data,
- NULL);
+ GrlMediaSourceMetadataSpec *ms = (GrlMediaSourceMetadataSpec *) user_data;
+ ms->callback (ms->source, media, ms->user_data, NULL);
+}
+
+static void
+build_media_from_entry_search_cb (GrlMedia *media, gpointer user_data)
+{
+ /*
+ * TODO: Async resolution of URL messes (or could mess) with the sorting,
+ * If we want to ensure a particular sorting or implement sorting
+ * mechanisms we should add code to handle that here so we emit items in
+ * the right order and not just when we got the URL resolved (would
+ * damage response time though).
+ */
+ OperationSpec *os = (OperationSpec *) user_data;
+ guint remaining;
+
+ if (os->emitted < os->count) {
+ remaining = os->count - os->emitted - 1;
+ os->callback (os->source,
+ os->operation_id,
+ media,
+ remaining,
+ os->user_data,
+ NULL);
+ if (remaining == 0) {
+ g_debug ("Unreffing spec in build_media_from_entry_search_cb");
+ operation_spec_unref (os);
+ } else {
+ os->emitted++;
+ }
+ }
}
static void
@@ -794,8 +863,8 @@ metadata_cb (GObject *object,
ms->callback (ms->source, ms->media, ms->user_data, error);
g_error_free (error);
} else {
- build_media_from_entry (ms->media, video, ms->keys);
- ms->callback (ms->source, ms->media, ms->user_data, NULL);
+ build_media_from_entry (ms->media, video, ms->keys,
+ build_media_from_entry_metadata_cb, ms);
}
if (video) {
@@ -811,11 +880,18 @@ search_progress_cb (GDataEntry *entry,
{
OperationSpec *os = (OperationSpec *) user_data;
if (index < count) {
- os->count = count - index - 1;
- process_entry (entry, os->count, os);
+ /* Keep track of the items we got here. Due to the asynchronous
+ * nature of build_media_from_entry(), when search_cb is invoked
+ * we have to check if we got as many results as we requested or
+ * not, and handle that situation properly */
+ os->matches++;
+ build_media_from_entry (NULL, entry, os->keys,
+ build_media_from_entry_search_cb, os);
} else {
g_warning ("Invalid index/count received grom libgdata, ignoring result");
}
+
+ /* The entry will be freed when freeing the feed in search_cb */
}
static void
@@ -825,6 +901,7 @@ search_cb (GObject *object, GAsyncResult *result, OperationSpec *os)
GDataFeed *feed;
GError *error = NULL;
+ gboolean need_extra_unref = FALSE;
GrlYoutubeSource *source = GRL_YOUTUBE_SOURCE (os->source);
feed = gdata_service_query_finish (source->service, result, &error);
@@ -834,9 +911,20 @@ search_cb (GObject *object, GAsyncResult *result, OperationSpec *os)
os->category_info->count = gdata_feed_get_total_results (feed);
}
- /* Should not be necessary, but just in case... */
- if (os->count > 0) {
- os->callback (os->source, os->operation_id, NULL, 0, os->user_data, NULL);
+ /* Check if we got as many results as we requested */
+ if (os->matches < os->count) {
+ os->count = os->matches;
+ /* In case we are resolving URLs asynchronously, from now on
+ * results will be sent with appropriate remaining, but it can
+ * also be the case that we have sent all the results already
+ * and the last one was sent with remaining>0, in that case
+ * we should send a finishing message now. */
+ if (os->emitted == os->count) {
+ g_debug ("sending finishing message");
+ os->callback (os->source, os->operation_id,
+ NULL, 0, os->user_data, NULL);
+ need_extra_unref = TRUE;
+ }
}
} else {
if (!error) {
@@ -848,11 +936,19 @@ search_cb (GObject *object, GAsyncResult *result, OperationSpec *os)
}
os->callback (os->source, os->operation_id, NULL, 0, os->user_data, error);
g_error_free (error);
+ need_extra_unref = TRUE;
}
if (feed)
g_object_unref (feed);
- free_operation_spec (os);
+
+ g_debug ("Unreffing spec in search_cb");
+ operation_spec_unref (os);
+ if (need_extra_unref) {
+ /* We did not free the spec in the emission callback, do it here */
+ g_debug ("need extra spec unref in search_cb");
+ operation_spec_unref (os);
+ }
}
static gboolean
@@ -974,7 +1070,7 @@ produce_from_directory (CategoryInfo *dir, guint dir_size, OperationSpec *os)
0,
os->user_data,
NULL);
- free_operation_spec (os);
+ operation_spec_unref (os);
} else {
index = os->skip;
remaining = MIN (dir_size - os->skip, os->count);
@@ -996,7 +1092,7 @@ produce_from_directory (CategoryInfo *dir, guint dir_size, OperationSpec *os)
NULL);
if (remaining == 0) {
- free_operation_spec (os);
+ operation_spec_unref (os);
}
} while (remaining > 0);
}
@@ -1023,9 +1119,21 @@ produce_from_feed (OperationSpec *os)
os->user_data,
error);
g_error_free (error);
- free_operation_spec (os);
+ operation_spec_unref (os);
return;
}
+ /* OPERATION_SPEC_REF_RATIONALE
+ * Depending on wether the URL has been requested, metadata resolution
+ * for each item in the result set may or may not be asynchronous.
+ * We cannot free the spec in search_cb because that may be called
+ * before the asynchronous URL resolution is finished, and we cannot
+ * do it in build_media_from_entry_search_cb either, because in the
+ * synchronous case (when we do not request URL) search_cb will
+ * be invoked after it.
+ * Thus, the solution is to increase the reference count here and
+ * have both places unreffing the spec, that way, no matter which
+ * is invoked last, the spec will be freed only once. */
+ operation_spec_ref (os);
service = GRL_YOUTUBE_SOURCE (os->source)->service;
query = gdata_query_new_with_limits (NULL , os->skip, os->count);
@@ -1064,10 +1172,13 @@ produce_from_category (OperationSpec *os)
os->user_data,
error);
g_error_free (error);
- free_operation_spec (os);
+ operation_spec_unref (os);
return;
}
+ /* Look for OPERATION_SPEC_REF_RATIONALE for details */
+ operation_spec_ref (os);
+
service = GRL_YOUTUBE_SOURCE (os->source)->service;
query = gdata_query_new_with_limits (NULL , os->skip, os->count);
os->category_info = &categories_dir[category_index];
@@ -1127,7 +1238,7 @@ grl_youtube_source_search (GrlMediaSource *source,
g_debug ("grl_youtube_source_search (%u, %u)", ss->skip, ss->count);
- os = g_slice_new0 (OperationSpec);
+ os = operation_spec_new ();
os->source = source;
os->operation_id = ss->search_id;
os->keys = ss->keys;
@@ -1137,6 +1248,9 @@ grl_youtube_source_search (GrlMediaSource *source,
os->user_data = ss->user_data;
os->error_code = GRL_ERROR_SEARCH_FAILED;
+ /* Look for OPERATION_SPEC_REF_RATIONALE for details */
+ operation_spec_ref (os);
+
query = gdata_query_new_with_limits (ss->text, os->skip, os->count);
gdata_youtube_service_query_videos_async (GDATA_YOUTUBE_SERVICE (GRL_YOUTUBE_SOURCE (source)->service),
query,
@@ -1159,7 +1273,7 @@ grl_youtube_source_browse (GrlMediaSource *source,
container_id = grl_media_get_id (bs->container);
- os = g_slice_new0 (OperationSpec);
+ os = operation_spec_new ();
os->source = bs->source;
os->operation_id = bs->browse_id;
os->container_id = container_id;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]