[totem] Bug 618958 — Pressing search in youtube search >1 times quickly crashes totem
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [totem] Bug 618958 — Pressing search in youtube search >1 times quickly crashes totem
- Date: Sat, 26 Jun 2010 12:43:56 +0000 (UTC)
commit a39491154701e801f3060abc0cab2e24df4c5a65
Author: Philip Withnall <philip tecnocode co uk>
Date: Sat Jun 26 13:41:39 2010 +0100
Bug 618958 â?? Pressing search in youtube search >1 times quickly crashes totem
Refactor the cancellable handling in the YouTube plugin so that it doesn't
use a single global cancellable instance (which didn't actually work anyway).
Closes: bgo#618958
src/plugins/youtube/totem-youtube.c | 205 +++++++++++++++++++++++------------
1 files changed, 133 insertions(+), 72 deletions(-)
---
diff --git a/src/plugins/youtube/totem-youtube.c b/src/plugins/youtube/totem-youtube.c
index a28c2f5..87f4562 100644
--- a/src/plugins/youtube/totem-youtube.c
+++ b/src/plugins/youtube/totem-youtube.c
@@ -371,10 +371,10 @@ impl_deactivate (PeasActivatable *plugin, GObject *totem)
totem_remove_sidebar_page (self->totem, "youtube");
for (i = 0; i < NUM_TREE_VIEWS; i++) {
- if (self->cancellable[i] != NULL) {
+ /* Cancel any queries which are still underway */
+ if (self->cancellable[i] != NULL)
g_cancellable_cancel (self->cancellable[i]);
- g_object_unref (self->cancellable[i]);
- }
+
if (self->query[i] != NULL)
g_object_unref (self->query[i]);
}
@@ -456,22 +456,13 @@ increment_progress_bar_fraction (TotemYouTubePlugin *self, guint tree_view)
gdk_window_set_cursor (gtk_widget_get_window (self->vbox), NULL);
gtk_progress_bar_set_text (self->progress_bar[tree_view], "");
gtk_progress_bar_set_fraction (self->progress_bar[tree_view], 0.0);
-
- /* Disable the "Cancel" button, if it applies to the current tree view */
- if (self->current_tree_view == tree_view)
- gtk_widget_set_sensitive (self->cancel_button, FALSE);
-
- /* Unref cancellable */
- if (self->cancellable[tree_view] != NULL)
- g_object_unref (self->cancellable[tree_view]);
- self->cancellable[tree_view] = NULL;
}
}
typedef struct {
TotemYouTubePlugin *plugin;
GDataEntry *entry;
- GtkTreeIter iter;
+ GtkTreePath *path;
guint tree_view;
} TParamData;
@@ -483,6 +474,7 @@ resolve_t_param_cb (GObject *source_object, GAsyncResult *result, TParamData *da
gsize length;
GMatchInfo *match_info;
GError *error = NULL;
+ GtkTreeIter iter;
TotemYouTubePlugin *self = data->plugin;
/* Finish loading the page */
@@ -547,8 +539,10 @@ resolve_t_param_cb (GObject *source_object, GAsyncResult *result, TParamData *da
g_free (contents);
/* Update the tree view with the new MRL */
- gtk_list_store_set (self->list_store[data->tree_view], &(data->iter), 2, video_uri, -1);
- g_debug ("Updated list store with new video URI (\"%s\") for entry %s", video_uri, video_id);
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (self->list_store[data->tree_view]), &iter, data->path) == TRUE) {
+ gtk_list_store_set (self->list_store[data->tree_view], &iter, 2, video_uri, -1);
+ g_debug ("Updated list store with new video URI (\"%s\") for entry %s", video_uri, video_id);
+ }
g_free (video_uri);
@@ -558,11 +552,12 @@ free_data:
g_object_unref (data->plugin);
g_object_unref (data->entry);
+ gtk_tree_path_free (data->path);
g_slice_free (TParamData, data);
}
static void
-resolve_t_param (TotemYouTubePlugin *self, GDataEntry *entry, GtkTreeIter *iter, guint tree_view)
+resolve_t_param (TotemYouTubePlugin *self, GDataEntry *entry, GtkTreeIter *iter, guint tree_view, GCancellable *cancellable)
{
GDataLink *link;
GFile *video_page;
@@ -575,18 +570,19 @@ resolve_t_param (TotemYouTubePlugin *self, GDataEntry *entry, GtkTreeIter *iter,
data = g_slice_new (TParamData);
data->plugin = g_object_ref (self);
data->entry = g_object_ref (entry);
- data->iter = *iter;
+ data->path = gtk_tree_model_get_path (GTK_TREE_MODEL (self->list_store[tree_view]), iter);
data->tree_view = tree_view;
video_page = g_file_new_for_uri (gdata_link_get_uri (link));
- g_file_load_contents_async (video_page, self->cancellable[tree_view], (GAsyncReadyCallback) resolve_t_param_cb, data);
+ g_file_load_contents_async (video_page, cancellable, (GAsyncReadyCallback) resolve_t_param_cb, data);
g_object_unref (video_page);
}
typedef struct {
TotemYouTubePlugin *plugin;
- GtkTreeIter iter;
+ GtkTreePath *path;
guint tree_view;
+ GCancellable *cancellable;
} ThumbnailData;
static void
@@ -594,6 +590,7 @@ thumbnail_loaded_cb (GObject *source_object, GAsyncResult *result, ThumbnailData
{
GdkPixbuf *thumbnail;
GError *error = NULL;
+ GtkTreeIter iter;
TotemYouTubePlugin *self = data->plugin;
/* Finish loading the thumbnail */
@@ -615,8 +612,10 @@ thumbnail_loaded_cb (GObject *source_object, GAsyncResult *result, ThumbnailData
g_debug ("Finished creating thumbnail from stream");
/* Update the tree view */
- gtk_list_store_set (self->list_store[data->tree_view], &(data->iter), 0, thumbnail, -1);
- g_debug ("Updated list store with new thumbnail");
+ if (gtk_tree_model_get_iter (GTK_TREE_MODEL (self->list_store[data->tree_view]), &iter, data->path) == TRUE) {
+ gtk_list_store_set (self->list_store[data->tree_view], &iter, 0, thumbnail, -1);
+ g_debug ("Updated list store with new thumbnail");
+ }
g_object_unref (thumbnail);
@@ -625,6 +624,8 @@ free_data:
increment_progress_bar_fraction (self, data->tree_view);
g_object_unref (data->plugin);
+ g_object_unref (data->cancellable);
+ gtk_tree_path_free (data->path);
g_slice_free (ThumbnailData, data);
}
@@ -634,7 +635,6 @@ thumbnail_opened_cb (GObject *source_object, GAsyncResult *result, ThumbnailData
GFile *thumbnail_file;
GFileInputStream *input_stream;
GError *error = NULL;
- TotemYouTubePlugin *self = data->plugin;
/* Finish opening the thumbnail */
thumbnail_file = G_FILE (source_object);
@@ -647,20 +647,40 @@ thumbnail_opened_cb (GObject *source_object, GAsyncResult *result, ThumbnailData
return;
}
+ /* NOTE: There's no need to reset the cancellable before using it again, as we'll have bailed before now if it was ever cancelled. */
+
g_debug ("Creating thumbnail from stream");
totem_gdk_pixbuf_new_from_stream_at_scale_async (G_INPUT_STREAM (input_stream), THUMBNAIL_WIDTH, -1, TRUE,
- self->cancellable[data->tree_view], (GAsyncReadyCallback) thumbnail_loaded_cb, data);
+ data->cancellable, (GAsyncReadyCallback) thumbnail_loaded_cb, data);
g_object_unref (input_stream);
}
typedef struct {
TotemYouTubePlugin *plugin;
guint tree_view;
+ GCancellable *query_cancellable;
+ GCancellable *t_param_cancellable;
+ GCancellable *thumbnail_cancellable;
} QueryData;
static void
+query_data_free (QueryData *data)
+{
+ if (data->thumbnail_cancellable != NULL)
+ g_object_unref (data->thumbnail_cancellable);
+ if (data->t_param_cancellable != NULL)
+ g_object_unref (data->t_param_cancellable);
+
+ g_object_unref (data->query_cancellable);
+ g_object_unref (data->plugin);
+
+ g_slice_free (QueryData, data);
+}
+
+static void
query_finished_cb (GObject *source_object, GAsyncResult *result, QueryData *data)
{
+ GtkWindow *window;
GDataFeed *feed;
GError *error = NULL;
TotemYouTubePlugin *self = data->plugin;
@@ -673,38 +693,45 @@ query_finished_cb (GObject *source_object, GAsyncResult *result, QueryData *data
self->progress_bar_increment[data->tree_view] = 1.0;
increment_progress_bar_fraction (self, data->tree_view);
- if (feed == NULL) {
- GtkWindow *window;
+ if (feed != NULL) {
+ /* Success! */
+ g_object_unref (feed);
+ query_data_free (data);
- /* Bail out if the operation was cancelled */
- if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) == TRUE) {
- g_error_free (error);
- goto free_data;
- }
+ return;
+ }
- /* Error! */
- window = totem_get_main_window (data->plugin->totem);
- if (g_error_matches (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR) == TRUE) {
- /* Hide the ugly technical message libgdata gives behind a nice one telling them it's out of date (which it likely is
- * if we're receiving a protocol error). */
- totem_interface_error (_("Error Searching for Videos"),
- _("The response from the server could not be understood. "
- "Please check you are running the latest version of libgdata."), window);
- } else {
- /* Spew out the error message as provided */
- totem_interface_error (_("Error Searching for Videos"), error->message, window);
- }
+ /* Bail out if the operation was cancelled */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED) == TRUE) {
+ /* Cancel the t-param and thumbnail threads, if applicable */
+ if (data->t_param_cancellable != NULL)
+ g_cancellable_cancel (data->t_param_cancellable);
+ if (data->thumbnail_cancellable != NULL)
+ g_cancellable_cancel (data->thumbnail_cancellable);
- g_object_unref (window);
- g_error_free (error);
- goto free_data;
+ goto finish;
}
- g_object_unref (feed);
+ /* Error! */
+ window = totem_get_main_window (data->plugin->totem);
+ if (g_error_matches (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_PROTOCOL_ERROR) == TRUE) {
+ /* Hide the ugly technical message libgdata gives behind a nice one telling them it's out of date (which it likely is
+ * if we're receiving a protocol error). */
+ totem_interface_error (_("Error Searching for Videos"),
+ _("The response from the server could not be understood. "
+ "Please check you are running the latest version of libgdata."), window);
+ } else {
+ /* Spew out the error message as provided */
+ totem_interface_error (_("Error Searching for Videos"), error->message, window);
+ }
-free_data:
- g_object_unref (data->plugin);
- g_slice_free (QueryData, data);
+ g_object_unref (window);
+
+finish:
+ g_error_free (error);
+ query_data_free (data);
+
+ return;
}
static void
@@ -718,9 +745,6 @@ query_progress_cb (GDataEntry *entry, guint entry_key, guint entry_count, QueryD
GtkProgressBar *progress_bar;
TotemYouTubePlugin *self = data->plugin;
- /* Check this query hasn't finished */
- g_assert (self->cancellable[data->tree_view] != NULL);
-
/* Add the entry to the tree view */
title = gdata_entry_get_title (entry);
id = gdata_youtube_video_get_video_id (GDATA_YOUTUBE_VIDEO (entry));
@@ -742,7 +766,9 @@ query_progress_cb (GDataEntry *entry, guint entry_key, guint entry_count, QueryD
gtk_progress_bar_set_fraction (progress_bar, gtk_progress_bar_get_fraction (progress_bar) + self->progress_bar_increment[data->tree_view]);
/* Resolve the t parameter for the video, which is required before it can be played */
- resolve_t_param (self, entry, &iter, data->tree_view);
+ /* This will be cancelled if the main query is cancelled, in query_finished_cb() */
+ data->t_param_cancellable = g_cancellable_new ();
+ resolve_t_param (self, entry, &iter, data->tree_view, data->t_param_cancellable);
/* Download the entry's thumbnail, ready for adding it to the tree view.
* Find the thumbnail size which is closest to the wanted size (THUMBNAIL_WIDTH), so that we:
@@ -774,52 +800,84 @@ query_progress_cb (GDataEntry *entry, guint entry_key, guint entry_count, QueryD
t_data = g_slice_new (ThumbnailData);
t_data->plugin = g_object_ref (self);
- t_data->iter = iter;
+ t_data->path = gtk_tree_model_get_path (GTK_TREE_MODEL (self->list_store[data->tree_view]), &iter);
t_data->tree_view = data->tree_view;
+ /* We can use the same cancellable for reading the file and making a pixbuf out of it, as they're consecutive operations */
+ /* This will be cancelled if the main query is cancelled, in query_finished_cb() */
+ data->thumbnail_cancellable = g_cancellable_new ();
+ t_data->cancellable = g_object_ref (data->thumbnail_cancellable);
+
g_debug ("Starting thumbnail download for entry %s", id);
thumbnail_file = g_file_new_for_uri (gdata_media_thumbnail_get_uri (thumbnail));
- g_file_read_async (thumbnail_file, G_PRIORITY_DEFAULT, self->cancellable[data->tree_view],
+ g_file_read_async (thumbnail_file, G_PRIORITY_DEFAULT, data->thumbnail_cancellable,
(GAsyncReadyCallback) thumbnail_opened_cb, t_data);
g_object_unref (thumbnail_file);
}
}
+/* Called when self->cancellable[tree_view] is destroyed (for either tree view) */
static void
-execute_query (TotemYouTubePlugin *self, guint tree_view, gboolean clear_tree_view)
+cancellable_notify_cb (TotemYouTubePlugin *self, GCancellable *old_cancellable)
{
- QueryData *data;
+ guint i;
+
+ /* Disable the "Cancel" button, if it applies to the current tree view */
+ if (self->cancellable[self->current_tree_view] == old_cancellable)
+ gtk_widget_set_sensitive (self->cancel_button, FALSE);
+ /* NULLify the cancellable */
+ for (i = 0; i < NUM_TREE_VIEWS; i++) {
+ if (self->cancellable[i] == old_cancellable)
+ self->cancellable[i] = NULL;
+ }
+}
+
+static void
+set_current_operation (TotemYouTubePlugin *self, guint tree_view, GCancellable *cancellable)
+{
/* Cancel previous searches on this tree view */
- if (self->cancellable[tree_view] != NULL) {
+ if (self->cancellable[tree_view] != NULL)
g_cancellable_cancel (self->cancellable[tree_view]);
- g_object_unref (self->cancellable[tree_view]);
- }
- /* Clear the tree views */
- if (clear_tree_view == TRUE)
- gtk_list_store_clear (self->list_store[tree_view]);
+ /* Make this the current cancellable action for the given tab */
+ g_object_weak_ref (G_OBJECT (cancellable), (GWeakNotify) cancellable_notify_cb, self);
+ self->cancellable[tree_view] = cancellable;
- /* Do the query */
- self->cancellable[tree_view] = g_cancellable_new ();
+ /* Enable the "Cancel" button if it applies to the current tree view */
+ if (self->current_tree_view == tree_view)
+ gtk_widget_set_sensitive (self->cancel_button, TRUE);
+}
+
+static void
+execute_query (TotemYouTubePlugin *self, guint tree_view, gboolean clear_tree_view)
+{
+ QueryData *data;
+ /* Set up the query */
data = g_slice_new (QueryData);
data->plugin = g_object_ref (self);
data->tree_view = tree_view;
+ data->query_cancellable = g_cancellable_new ();
+ data->t_param_cancellable = NULL;
+ data->thumbnail_cancellable = NULL;
+
+ /* Make this the current cancellable action for the given tab */
+ set_current_operation (self, tree_view, data->query_cancellable);
+
+ /* Clear the tree views */
+ if (clear_tree_view == TRUE)
+ gtk_list_store_clear (self->list_store[tree_view]);
if (tree_view == SEARCH_TREE_VIEW) {
- gdata_youtube_service_query_videos_async (self->service, self->query[tree_view], self->cancellable[tree_view],
+ gdata_youtube_service_query_videos_async (self->service, self->query[tree_view], data->query_cancellable,
(GDataQueryProgressCallback) query_progress_cb, data,
(GAsyncReadyCallback) query_finished_cb, data);
} else {
- gdata_youtube_service_query_related_async (self->service, self->playing_video, self->query[tree_view], self->cancellable[tree_view],
+ gdata_youtube_service_query_related_async (self->service, self->playing_video, self->query[tree_view], data->query_cancellable,
(GDataQueryProgressCallback) query_progress_cb, data,
(GAsyncReadyCallback) query_finished_cb, data);
}
-
- /* Enable the "Cancel" button if it applies to the current tree view */
- if (self->current_tree_view == tree_view)
- gtk_widget_set_sensitive (self->cancel_button, TRUE);
}
void
@@ -874,7 +932,10 @@ search_button_clicked_cb (GtkButton *button, TotemYouTubePlugin *self)
void
cancel_button_clicked_cb (GtkButton *button, TotemYouTubePlugin *self)
{
- g_assert (self->cancellable[self->current_tree_view] != NULL);
+ /* It's possible for the operation to finish (and consequently the cancellable to disappear) while the GtkButton is deciding whether the
+ * user is actually pressing it (in its timeout). */
+ if (self->cancellable[self->current_tree_view] == NULL)
+ return;
g_debug ("Cancelling search");
g_cancellable_cancel (self->cancellable[self->current_tree_view]);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]