[totem] Bug 559628 — Support async loading of playlists



commit 024de838bd0f099cdc14b5082e23a60490d9159e
Author: Philip Withnall <philip tecnocode co uk>
Date:   Sun Apr 25 14:43:00 2010 +0100

    Bug 559628 â?? Support async loading of playlists
    
    Add async playlist support. This converts most calls to the playlist
    add MRL function to be async, dealing with the cursor appropriately
    (such that concurrent operations using a busy cursor don't trample on
    each others' cursors when they finish). Closes: bgo#559628

 src/plugins/publish/totem-publish.c |    2 +-
 src/totem-object.c                  |   79 ++++++++++++-----
 src/totem-playlist.c                |  166 +++++++++++++++++++++++++----------
 src/totem-playlist.h                |   18 +++--
 src/totem-session.c                 |    4 +-
 src/totem-video-list.c              |    2 +-
 6 files changed, 192 insertions(+), 79 deletions(-)
---
diff --git a/src/plugins/publish/totem-publish.c b/src/plugins/publish/totem-publish.c
index 8399d64..20ac7f5 100644
--- a/src/plugins/publish/totem-publish.c
+++ b/src/plugins/publish/totem-publish.c
@@ -434,7 +434,7 @@ totem_publish_plugin_load_playlist (TotemPublishPlugin   *self,
 			g_free (key);
 
 			if (mrl)
-				totem_playlist_add_mrl (self->totem->playlist, mrl, title);
+				totem_playlist_add_mrl (self->totem->playlist, mrl, title, FALSE, NULL, NULL, NULL);
 
 			g_free (title);
 			g_free (mrl);
diff --git a/src/totem-object.c b/src/totem-object.c
index 57af6e8..74aa06f 100644
--- a/src/totem-object.c
+++ b/src/totem-object.c
@@ -447,6 +447,45 @@ totem_get_current_time (Totem *totem)
 	return bacon_video_widget_get_current_time (totem->bvw);
 }
 
+typedef struct {
+	Totem *totem;
+	gchar *uri;
+	gchar *display_name;
+	gboolean add_to_recent;
+} AddToPlaylistData;
+
+static void
+add_to_playlist_and_play_cb (TotemPlaylist *playlist, GAsyncResult *async_result, AddToPlaylistData *data)
+{
+	int end;
+	gboolean playlist_changed;
+
+	playlist_changed = totem_playlist_add_mrl_finish (playlist, async_result);
+
+	if (data->add_to_recent != FALSE)
+		gtk_recent_manager_add_item (data->totem->recent_manager, data->uri);
+	end = totem_playlist_get_last (playlist);
+
+	totem_signal_unblock_by_data (playlist, data->totem);
+
+	if (playlist_changed && end != -1) {
+		char *mrl, *subtitle;
+
+		subtitle = NULL;
+		totem_playlist_set_current (playlist, end);
+		mrl = totem_playlist_get_current_mrl (playlist, &subtitle);
+		totem_action_set_mrl_and_play (data->totem, mrl, subtitle);
+		g_free (mrl);
+		g_free (subtitle);
+	}
+
+	/* Free the closure data */
+	g_object_unref (data->totem);
+	g_free (data->uri);
+	g_free (data->display_name);
+	g_slice_free (AddToPlaylistData, data);
+}
+
 /**
  * totem_add_to_playlist_and_play:
  * @totem: a #TotemObject
@@ -462,28 +501,21 @@ totem_add_to_playlist_and_play (Totem *totem,
 				const char *display_name,
 				gboolean add_to_recent)
 {
-	gboolean playlist_changed;
-	int end;
+	AddToPlaylistData *data;
 
+	/* Block all signals from the playlist until we're finished. They're unblocked in the callback, add_to_playlist_and_play_cb.
+	 * There are no concurrency issues here, since blocking the signals multiple times should require them to be unblocked the
+	 * same number of times before they fire again. */
 	totem_signal_block_by_data (totem->playlist, totem);
 
-	playlist_changed = totem_playlist_add_mrl_with_cursor (totem->playlist, uri, display_name);
-	if (add_to_recent != FALSE)
-		gtk_recent_manager_add_item (totem->recent_manager, uri);
-	end = totem_playlist_get_last (totem->playlist);
-
-	totem_signal_unblock_by_data (totem->playlist, totem);
-
-	if (playlist_changed && end != -1) {
-		char *mrl, *subtitle;
+	data = g_slice_new (AddToPlaylistData);
+	data->totem = g_object_ref (totem);
+	data->uri = g_strdup (uri);
+	data->display_name = g_strdup (display_name);
+	data->add_to_recent = add_to_recent;
 
-		subtitle = NULL;
-		totem_playlist_set_current (totem->playlist, end);
-		mrl = totem_playlist_get_current_mrl (totem->playlist, &subtitle);
-		totem_action_set_mrl_and_play (totem, mrl, subtitle);
-		g_free (mrl);
-		g_free (subtitle);
-	}
+	totem_playlist_add_mrl (totem->playlist, uri, display_name, TRUE,
+	                        NULL, (GAsyncReadyCallback) add_to_playlist_and_play_cb, data);
 }
 
 /**
@@ -2217,7 +2249,7 @@ totem_action_drop_files (Totem *totem, GtkSelectionData *data,
 			}
 		}
 
-		totem_playlist_add_mrl (totem->playlist, filename, title);
+		totem_playlist_add_mrl (totem->playlist, filename, title, FALSE, NULL, NULL, NULL);
 	}
 
 bail:
@@ -2773,9 +2805,10 @@ totem_action_open_files_list (Totem *totem, GSList *list)
 				totem_action_load_media_device (totem, data);
 				changed = TRUE;
 			} else if (g_str_has_prefix (filename, "dvb:/") != FALSE) {
-				totem_playlist_add_mrl (totem->playlist, data, NULL);
+				totem_playlist_add_mrl (totem->playlist, data, NULL, FALSE, NULL, NULL, NULL);
 				changed = TRUE;
-			} else if (totem_playlist_add_mrl (totem->playlist, filename, NULL) != FALSE) {
+			} else {
+				totem_playlist_add_mrl (totem->playlist, filename, NULL, FALSE, NULL, NULL, NULL);
 				changed = TRUE;
 			}
 		}
@@ -3055,7 +3088,7 @@ totem_action_remote (Totem *totem, TotemRemoteCommand cmd, const char *url)
 		break;
 	case TOTEM_REMOTE_COMMAND_ENQUEUE:
 		g_assert (url != NULL);
-		totem_playlist_add_mrl_with_cursor (totem->playlist, url, NULL);
+		totem_playlist_add_mrl (totem->playlist, url, NULL, TRUE, NULL, NULL, NULL);
 		break;
 	case TOTEM_REMOTE_COMMAND_REPLACE:
 		totem_playlist_clear (totem->playlist);
@@ -3072,7 +3105,7 @@ totem_action_remote (Totem *totem, TotemRemoteCommand cmd, const char *url)
 			/* FIXME b0rked */
 			totem_action_play_media (totem, MEDIA_TYPE_VCD, NULL);
 		} else {
-			totem_playlist_add_mrl_with_cursor (totem->playlist, url, NULL);
+			totem_playlist_add_mrl (totem->playlist, url, NULL, TRUE, NULL, NULL, NULL);
 		}
 		break;
 	case TOTEM_REMOTE_COMMAND_SHOW:
diff --git a/src/totem-playlist.c b/src/totem-playlist.c
index 78479b7..06034b7 100644
--- a/src/totem-playlist.c
+++ b/src/totem-playlist.c
@@ -95,6 +95,9 @@ struct TotemPlaylistPrivate
 	GtkTreePath *tree_path;
 	GtkTreeViewDropPosition drop_pos;
 
+	/* Cursor ref: 0 if the cursor is unbusy; positive numbers indicate the number of nested calls to set_waiting_cursor() */
+	guint cursor_ref;
+
 	/* This is a scratch list for when we're removing files */
 	GList *list;
 	guint current_to_be_removed : 1;
@@ -216,21 +219,17 @@ totem_playlist_get_toplevel (TotemPlaylist *playlist)
 }
 
 static void
-totem_playlist_set_waiting_cursor (TotemPlaylist *playlist)
+set_waiting_cursor (TotemPlaylist *playlist)
 {
-	GtkWidget *parent;
-
-	parent = GTK_WIDGET (totem_playlist_get_toplevel (playlist));
-	totem_gdk_window_set_waiting_cursor (gtk_widget_get_window (parent));
+	totem_gdk_window_set_waiting_cursor (gtk_widget_get_window (GTK_WIDGET (totem_playlist_get_toplevel (playlist))));
+	playlist->priv->cursor_ref++;
 }
 
 static void
-totem_playlist_unset_waiting_cursor (TotemPlaylist *playlist)
+unset_waiting_cursor (TotemPlaylist *playlist)
 {
-	GtkWidget *parent;
-
-	parent = GTK_WIDGET (totem_playlist_get_toplevel (playlist));
-	gdk_window_set_cursor (gtk_widget_get_window (parent), NULL);
+	if (--playlist->priv->cursor_ref < 1)
+		gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (totem_playlist_get_toplevel (playlist))), NULL);
 }
 
 static void
@@ -486,6 +485,15 @@ gtk_tree_selection_has_selected (GtkTreeSelection *selection)
 }
 
 static void
+drop_finished_cb (TotemPlaylist *playlist, GAsyncResult *result, gpointer user_data)
+{
+	/* Emit the "changed" signal once the last dropped MRL has been added to the playlist */
+	g_signal_emit (G_OBJECT (playlist),
+	               totem_playlist_table_signals[CHANGED], 0,
+	               NULL);
+}
+
+static void
 drop_cb (GtkWidget        *widget,
          GdkDragContext   *context, 
 	 gint              x,
@@ -525,8 +533,6 @@ drop_cb (GtkWidget        *widget,
 		return;
 	}
 
-	totem_playlist_set_waiting_cursor (playlist);
-
 	playlist->priv->tree_path = gtk_tree_path_new ();
 	gtk_tree_view_get_dest_row_at_pos (GTK_TREE_VIEW (playlist->priv->treeview),
 					   x, y,
@@ -559,7 +565,12 @@ drop_cb (GtkWidget        *widget,
 			}
 		}
 
-		totem_playlist_add_mrl (playlist, filename, title);
+		/* Add the MRL to the playlist asynchronously. If it's the last MRL, emit the "changed"
+		 * signal once we're done adding it */
+		if (p->next == NULL)
+			totem_playlist_add_mrl (playlist, filename, title, TRUE, NULL, (GAsyncReadyCallback) drop_finished_cb, NULL);
+		else
+			totem_playlist_add_mrl (playlist, filename, title, TRUE, NULL, NULL, NULL);
 
 		g_free (filename);
 	}
@@ -569,12 +580,6 @@ drop_cb (GtkWidget        *widget,
 	gtk_drag_finish (context, TRUE, FALSE, _time);
 	gtk_tree_path_free (playlist->priv->tree_path);
 	playlist->priv->tree_path = NULL;
-
-	totem_playlist_unset_waiting_cursor (playlist);
-
-	g_signal_emit (G_OBJECT (playlist),
-			totem_playlist_table_signals[CHANGED], 0,
-			NULL);
 }
 
 void
@@ -851,22 +856,18 @@ totem_playlist_add_files (GtkWidget *widget, TotemPlaylist *playlist)
 {
 	GSList *filenames, *l;
 
-	filenames = totem_add_files (totem_playlist_get_toplevel (playlist),
-			NULL);
+	filenames = totem_add_files (totem_playlist_get_toplevel (playlist), NULL);
 	if (filenames == NULL)
 		return;
 
-	totem_playlist_set_waiting_cursor (playlist);
-
 	for (l = filenames; l != NULL; l = l->next) {
 		char *mrl;
 
 		mrl = l->data;
-		totem_playlist_add_mrl (playlist, mrl, NULL);
+		totem_playlist_add_mrl (playlist, mrl, NULL, TRUE, NULL, NULL, NULL);
 		g_free (mrl);
 	}
 
-	totem_playlist_unset_waiting_cursor (playlist);
 	g_slist_free (filenames);
 }
 
@@ -1822,38 +1823,36 @@ totem_playlist_add_one_mrl (TotemPlaylist *playlist,
 	return TRUE;
 }
 
-gboolean
-totem_playlist_add_mrl_with_cursor (TotemPlaylist *playlist, const char *mrl,
-				    const char *display_name)
-{
-	gboolean retval;
-
-	totem_playlist_set_waiting_cursor (playlist);
-	retval = totem_playlist_add_mrl (playlist, mrl, display_name);
-	totem_playlist_unset_waiting_cursor (playlist);
+typedef struct {
+	GAsyncReadyCallback callback;
+	gpointer user_data;
+	gboolean cursor;
+	TotemPlaylist *playlist;
+	gchar *mrl;
+	gchar *display_name;
+} AddMrlData;
 
-	return retval;
+static void
+add_mrl_data_free (AddMrlData *data)
+{
+	g_object_unref (data->playlist);
+	g_free (data->mrl);
+	g_free (data->display_name);
+	g_slice_free (AddMrlData, data);
 }
 
-gboolean
-totem_playlist_add_mrl (TotemPlaylist *playlist, const char *mrl,
-			const char *display_name)
+static gboolean
+handle_parse_result (TotemPlParserResult res, TotemPlaylist *playlist, const gchar *mrl, const gchar *display_name)
 {
-	TotemPlParserResult res;
-
-	g_return_val_if_fail (mrl != NULL, FALSE);
-
-	res = totem_pl_parser_parse (playlist->priv->parser, mrl, FALSE);
-
 	if (res == TOTEM_PL_PARSER_RESULT_UNHANDLED)
 		return totem_playlist_add_one_mrl (playlist, mrl, display_name);
-	if (res == TOTEM_PL_PARSER_RESULT_ERROR)
-	{
+	if (res == TOTEM_PL_PARSER_RESULT_ERROR) {
 		char *msg;
 
-		msg = g_strdup_printf (_("The playlist '%s' could not be parsed, it might be damaged."), display_name ? display_name : mrl);
+		msg = g_strdup_printf (_("The playlist '%s' could not be parsed. It might be damaged."), display_name ? display_name : mrl);
 		totem_playlist_error (_("Playlist error"), msg, playlist);
 		g_free (msg);
+
 		return FALSE;
 	}
 	if (res == TOTEM_PL_PARSER_RESULT_IGNORED)
@@ -1862,6 +1861,79 @@ totem_playlist_add_mrl (TotemPlaylist *playlist, const char *mrl,
 	return TRUE;
 }
 
+static void
+add_mrl_cb (TotemPlParser *parser, GAsyncResult *result, AddMrlData *data)
+{
+	TotemPlParserResult res;
+	GSimpleAsyncResult *async_result;
+	GError *error = NULL;
+
+	/* Finish parsing the playlist */
+	res = totem_pl_parser_parse_finish (parser, result, &error);
+
+	/* Remove the cursor, if one was set */
+	if (data->cursor)
+		unset_waiting_cursor (data->playlist);
+
+	/* Create an async result which will return the result to the code which called totem_playlist_add_mrl() */
+	if (error != NULL)
+		async_result = g_simple_async_result_new_from_error (G_OBJECT (data->playlist), data->callback, data->user_data, error);
+	else
+		async_result = g_simple_async_result_new (G_OBJECT (data->playlist), data->callback, data->user_data, totem_playlist_add_mrl);
+
+	/* Handle the various return cases from the playlist parser */
+	g_simple_async_result_set_op_res_gboolean (async_result, handle_parse_result (res, data->playlist, data->mrl, data->display_name));
+
+	/* Free the closure's data, now that we're finished with it */
+	add_mrl_data_free (data);
+
+	/* Synchronously call the calling code's callback function (i.e. what was passed to totem_playlist_add_mrl()'s @callback parameter)
+	 * in the main thread to return the result */
+	g_simple_async_result_complete (async_result);
+}
+
+void
+totem_playlist_add_mrl (TotemPlaylist *playlist, const char *mrl, const char *display_name, gboolean cursor,
+                        GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+	AddMrlData *data;
+
+	g_return_if_fail (mrl != NULL);
+
+	/* Display a waiting cursor if required */
+	if (cursor)
+		set_waiting_cursor (playlist);
+
+	/* Build the data struct to pass to the callback function */
+	data = g_slice_new (AddMrlData);
+	data->callback = callback;
+	data->user_data = user_data;
+	data->cursor = cursor;
+	data->playlist = g_object_ref (playlist);
+	data->mrl = g_strdup (mrl);
+	data->display_name = g_strdup (display_name);
+
+	/* Start parsing the playlist. Once this is complete, add_mrl_cb() is called, which will interpret the results and call @callback to
+	 * finish the process. */
+	totem_pl_parser_parse_async (playlist->priv->parser, mrl, FALSE, cancellable, (GAsyncReadyCallback) add_mrl_cb, data);
+}
+
+gboolean
+totem_playlist_add_mrl_finish (TotemPlaylist *playlist, GAsyncResult *result)
+{
+	g_assert (g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)) == totem_playlist_add_mrl);
+
+	return g_simple_async_result_get_op_res_gboolean (G_SIMPLE_ASYNC_RESULT (result));
+}
+
+gboolean
+totem_playlist_add_mrl_sync (TotemPlaylist *playlist, const char *mrl, const char *display_name)
+{
+	g_return_val_if_fail (mrl != NULL, FALSE);
+
+	return handle_parse_result (totem_pl_parser_parse (playlist->priv->parser, mrl, FALSE), playlist, mrl, display_name);
+}
+
 static gboolean
 totem_playlist_clear_cb (GtkTreeModel *model,
 			 GtkTreePath  *path,
diff --git a/src/totem-playlist.h b/src/totem-playlist.h
index 0ec018b..d7e5a65 100644
--- a/src/totem-playlist.h
+++ b/src/totem-playlist.h
@@ -87,12 +87,18 @@ GtkWidget *totem_playlist_new      (void);
  * @display_name is if you have a preferred display string for the mrl,
  * NULL otherwise
  */
-gboolean totem_playlist_add_mrl  (TotemPlaylist *playlist,
-				  const char *mrl,
-				  const char *display_name);
-gboolean totem_playlist_add_mrl_with_cursor (TotemPlaylist *playlist,
-					     const char *mrl,
-					     const char *display_name);
+void totem_playlist_add_mrl (TotemPlaylist *playlist,
+                             const char *mrl,
+                             const char *display_name,
+                             gboolean cursor,
+                             GCancellable *cancellable,
+                             GAsyncReadyCallback callback,
+                             gpointer user_data);
+gboolean totem_playlist_add_mrl_finish (TotemPlaylist *playlist,
+                                        GAsyncResult *result);
+gboolean totem_playlist_add_mrl_sync (TotemPlaylist *playlist,
+                                      const char *mrl,
+                                      const char *display_name);
 
 void totem_playlist_save_current_playlist (TotemPlaylist *playlist,
 					   const char *output);
diff --git a/src/totem-session.c b/src/totem-session.c
index 1962246..ad272f0 100644
--- a/src/totem-session.c
+++ b/src/totem-session.c
@@ -147,7 +147,9 @@ totem_session_restore (Totem *totem, char **filenames)
 
 	totem_signal_block_by_data (totem->playlist, totem);
 
-	if (totem_playlist_add_mrl_with_cursor (totem->playlist, uri, NULL) == FALSE) {
+	/* Possibly the only place in Totem where it makes sense to add an MRL to the playlist synchronously, since we haven't yet entered
+	 * the GTK+ main loop, and thus can't freeze the application. */
+	if (totem_playlist_add_mrl_sync (totem->playlist, uri, NULL) == FALSE) {
 		totem_signal_unblock_by_data (totem->playlist, totem);
 		totem_action_set_mrl (totem, NULL, NULL);
 		g_free (uri);
diff --git a/src/totem-video-list.c b/src/totem-video-list.c
index 2bf6c1f..b5c61e2 100644
--- a/src/totem-video-list.c
+++ b/src/totem-video-list.c
@@ -482,7 +482,7 @@ add_to_playlist_action_callback (GtkAction *action, TotemVideoList *self)
 			continue;
 		}
 
-		totem_playlist_add_mrl_with_cursor (playlist, mrl, display_name);
+		totem_playlist_add_mrl (playlist, mrl, display_name, TRUE, NULL, NULL, NULL);
 
 		g_free (mrl);
 		g_free (display_name);



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