[libgdata] youtube: Add gdata_youtube_service_upload_video_async()



commit 85a249181981af3333673d9e1ea4e76c2b691c73
Author: Philip Withnall <philip tecnocode co uk>
Date:   Thu Oct 28 10:10:17 2010 +0100

    youtube: Add gdata_youtube_service_upload_video_async()
    
    This includes tests and documentation.

 docs/reference/gdata-sections.txt              |    2 +
 gdata/gdata.symbols                            |    2 +
 gdata/services/youtube/gdata-youtube-service.c |  213 +++++++++++++++++++++---
 gdata/services/youtube/gdata-youtube-service.h |    4 +
 gdata/tests/youtube.c                          |   53 ++++++
 5 files changed, 252 insertions(+), 22 deletions(-)
---
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 68691c4..7e8a956 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -188,6 +188,8 @@ gdata_youtube_service_query_related_async
 gdata_youtube_service_query_standard_feed
 gdata_youtube_service_query_standard_feed_async
 gdata_youtube_service_upload_video
+gdata_youtube_service_upload_video_async
+gdata_youtube_service_upload_video_finish
 gdata_youtube_service_get_categories
 gdata_youtube_service_get_categories_async
 gdata_youtube_service_get_categories_finish
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index 5cee491..5ab8320 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -852,3 +852,5 @@ gdata_contacts_service_query_groups
 gdata_contacts_service_query_groups_async
 gdata_contacts_service_insert_group
 gdata_contacts_service_insert_group_async
+gdata_youtube_service_upload_video_async
+gdata_youtube_service_upload_video_finish
diff --git a/gdata/services/youtube/gdata-youtube-service.c b/gdata/services/youtube/gdata-youtube-service.c
index d285b69..8fcc79e 100644
--- a/gdata/services/youtube/gdata-youtube-service.c
+++ b/gdata/services/youtube/gdata-youtube-service.c
@@ -651,6 +651,46 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu
 	                           GDATA_TYPE_YOUTUBE_VIDEO, cancellable, progress_callback, progress_user_data, callback, user_data);
 }
 
+static GOutputStream *
+get_file_output_stream (GDataYouTubeService *self, GDataYouTubeVideo *video_entry, GFile *video_data, GError **error)
+{
+	GFileInfo *file_info = NULL;
+	const gchar *slug = NULL, *content_type = NULL;
+	GOutputStream *output_stream;
+
+	file_info = g_file_query_info (video_data, "standard::display-name,standard::content-type", G_FILE_QUERY_INFO_NONE, NULL, error);
+	if (file_info == NULL)
+		return NULL;
+
+	slug = g_file_info_get_display_name (file_info);
+	content_type = g_file_info_get_content_type (file_info);
+
+	/* Streaming upload support using GDataUploadStream; automatically handles the XML and multipart stuff for us */
+	output_stream = gdata_upload_stream_new (GDATA_SERVICE (self), SOUP_METHOD_POST,
+	                                         "http://uploads.gdata.youtube.com/feeds/api/users/default/uploads";,
+	                                         GDATA_ENTRY (video_entry), slug, content_type);
+	g_object_unref (file_info);
+
+	return output_stream;
+}
+
+static GDataYouTubeVideo *
+parse_spliced_stream (GOutputStream *output_stream, GError **error)
+{
+	const gchar *response_body;
+	gssize response_length;
+	GDataYouTubeVideo *new_entry;
+
+	/* Get the response from the server */
+	response_body = gdata_upload_stream_get_response (GDATA_UPLOAD_STREAM (output_stream), &response_length);
+	g_assert (response_body != NULL && response_length > 0);
+
+	/* Parse the response to produce a GDataPicasaWebFile */
+	new_entry = GDATA_YOUTUBE_VIDEO (gdata_parsable_new_from_xml (GDATA_TYPE_YOUTUBE_VIDEO, response_body, (gint) response_length, error));
+
+	return new_entry;
+}
+
 /**
  * gdata_youtube_service_upload_video:
  * @self: a #GDataYouTubeService
@@ -672,13 +712,9 @@ gdata_youtube_service_query_related_async (GDataYouTubeService *self, GDataYouTu
 GDataYouTubeVideo *
 gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo *video, GFile *video_file, GCancellable *cancellable, GError **error)
 {
-	/* TODO: Async variant */
 	GDataYouTubeVideo *new_entry;
 	GOutputStream *output_stream;
 	GInputStream *input_stream;
-	const gchar *slug, *content_type, *response_body;
-	gssize response_length;
-	GFileInfo *file_info;
 	GError *child_error = NULL;
 
 	g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
@@ -699,19 +735,7 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo
 		return NULL;
 	}
 
-	file_info = g_file_query_info (video_file, "standard::display-name,standard::content-type", G_FILE_QUERY_INFO_NONE, NULL, error);
-	if (file_info == NULL)
-		return NULL;
-
-	slug = g_file_info_get_display_name (file_info);
-	content_type = g_file_info_get_content_type (file_info);
-
-	/* Streaming upload support using GDataUploadStream; automatically handles the XML and multipart stuff for us */
-	output_stream = gdata_upload_stream_new (GDATA_SERVICE (self), SOUP_METHOD_POST,
-	                                         "http://uploads.gdata.youtube.com/feeds/api/users/default/uploads";,
-	                                         GDATA_ENTRY (video), slug, content_type);
-
-	g_object_unref (file_info);
+	output_stream = get_file_output_stream (self, video, video_file, error);
 	if (output_stream == NULL)
 		return NULL;
 
@@ -733,17 +757,162 @@ gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo
 		return NULL;
 	}
 
-	/* Get and parse the response from the server */
-	response_body = gdata_upload_stream_get_response (GDATA_UPLOAD_STREAM (output_stream), &response_length);
-	g_assert (response_body != NULL && response_length > 0);
-
-	new_entry = GDATA_YOUTUBE_VIDEO (gdata_parsable_new_from_xml (GDATA_TYPE_YOUTUBE_VIDEO, response_body, (gint) response_length, error));
+	new_entry = parse_spliced_stream (output_stream, error);
 	g_object_unref (output_stream);
 
 	return new_entry;
 }
 
 /**
+ * gdata_youtube_service_upload_video_finish:
+ * @self: a #GDataYouTubeService
+ * @result: a #GSimpleAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * This should be called to obtain the result of a call to gdata_youtube_service_upload_video_async() and to check for errors.
+ *
+ * If there is a problem reading the subect file's data, an error from g_output_stream_splice() or g_file_query_info() will be returned. Other errors
+ * from #GDataServiceError can be returned for other exceptional conditions, as determined by the server.
+ *
+ * If the video to upload has already been inserted, a %GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED error will be set. If no user is authenticated with
+ * the service when trying to upload it, %GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED will be set.
+ *
+ * Return value: (transfer full): the inserted #GDataYouTubeVideo; unref with g_object_unref()
+ *
+ * Since: 0.8.0
+ */
+GDataYouTubeVideo *
+gdata_youtube_service_upload_video_finish (GDataYouTubeService *self, GAsyncResult *result, GError **error)
+{
+	g_return_val_if_fail (GDATA_IS_YOUTUBE_SERVICE (self), NULL);
+	g_return_val_if_fail (G_IS_ASYNC_RESULT (result), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+	if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+		return NULL;
+
+	g_assert (gdata_youtube_service_upload_video_async == g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)));
+
+	return GDATA_YOUTUBE_VIDEO (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
+}
+
+typedef struct {
+	GDataYouTubeService *service;
+	GAsyncReadyCallback callback;
+	gpointer user_data;
+} UploadVideoAsyncData;
+
+static void
+upload_video_async_data_free (UploadVideoAsyncData *data)
+{
+	g_object_unref (data->service);
+	g_slice_free (UploadVideoAsyncData, data);
+}
+
+static void
+upload_video_async_cb (GOutputStream *output_stream, GAsyncResult *result, UploadVideoAsyncData *data)
+{
+	GError *error = NULL;
+	GDataYouTubeVideo *video = NULL;
+	GSimpleAsyncResult *async_result;
+
+	g_output_stream_splice_finish (output_stream, result, &error);
+
+	/* If we're error free, parse the file from the stream */
+	if (error == NULL)
+		video = parse_spliced_stream (output_stream, &error);
+
+	if (error == NULL && video != NULL) {
+		async_result = g_simple_async_result_new (G_OBJECT (data->service), (GAsyncReadyCallback) data->callback,
+		                                          data->user_data, gdata_youtube_service_upload_video_async);
+	} else {
+		async_result = g_simple_async_result_new_from_error (G_OBJECT (data->service), (GAsyncReadyCallback) data->callback,
+		                                                     data->user_data, error);
+	}
+
+	g_simple_async_result_set_op_res_gpointer (async_result, video, NULL);
+
+	g_simple_async_result_complete (async_result);
+
+	upload_video_async_data_free (data);
+}
+
+/**
+ * gdata_youtube_service_upload_video_async:
+ * @self: a #GDataYouTubeService
+ * @video_entry: a #GDataYouTubeVideo to insert
+ * @video_data: the actual file to upload
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the upload is finished
+ * @user_data: (closure): data to pass to the @callback function
+ *
+ * Uploads a video to YouTube asynchronously, using the @video_data from disk and the metadata from @video_entry. A user must be authenticated to use
+ * this function. Note that uploaded videos aren't publicly visible on YouTube immediately; they need to go through a moderation queue first.
+ *
+ * @callback should call gdata_youtube_service_upload_video_finish() to obtain a #GDataYouTubeVideo representing the uploaded video and check for
+ * possible errors.
+ *
+ * Since: 0.8.0
+ **/
+void
+gdata_youtube_service_upload_video_async (GDataYouTubeService *self, GDataYouTubeVideo *video_entry, GFile *video_data, GCancellable *cancellable,
+                                          GAsyncReadyCallback callback, gpointer user_data)
+{
+	GOutputStream *output_stream;
+	GInputStream *input_stream;
+	UploadVideoAsyncData *data;
+	GSimpleAsyncResult *result;
+	GError *error = NULL;
+
+	g_return_if_fail (GDATA_IS_YOUTUBE_SERVICE (self));
+	g_return_if_fail (GDATA_IS_YOUTUBE_VIDEO (video_entry));
+	g_return_if_fail (G_IS_FILE (video_data));
+	g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+	if (gdata_entry_is_inserted (GDATA_ENTRY (video_entry)) == TRUE) {
+		g_set_error_literal (&error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
+		                     _("The entry has already been inserted."));
+		goto error;
+	}
+
+	if (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
+		g_set_error_literal (&error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+		                     _("You must be authenticated to upload a video."));
+		goto error;
+	}
+
+	/* Prepare and retrieve a #GDataOutputStream for the file and its data */
+	output_stream = get_file_output_stream (self, video_entry, video_data, &error);
+	if (output_stream == NULL)
+		goto error;
+
+	/* Pipe the input file to the upload stream */
+	input_stream = G_INPUT_STREAM (g_file_read (video_data, cancellable, &error));
+	if (input_stream == NULL) {
+		g_object_unref (output_stream);
+		goto error;
+	}
+
+	data = g_slice_new (UploadVideoAsyncData);
+	data->service = g_object_ref (self);
+	data->callback = callback;
+	data->user_data = user_data;
+
+	/* Actually transfer the data */
+	g_output_stream_splice_async (output_stream, input_stream, G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
+	                              0, cancellable, (GAsyncReadyCallback) upload_video_async_cb, data);
+
+	g_object_unref (input_stream);
+	g_object_unref (output_stream);
+
+	return;
+
+error:
+	result = g_simple_async_result_new_from_error (G_OBJECT (self), callback, user_data, error);
+	g_simple_async_result_complete (result);
+}
+
+/**
  * gdata_youtube_service_get_developer_key:
  * @self: a #GDataYouTubeService
  *
diff --git a/gdata/services/youtube/gdata-youtube-service.h b/gdata/services/youtube/gdata-youtube-service.h
index 19566c9..67c1427 100644
--- a/gdata/services/youtube/gdata-youtube-service.h
+++ b/gdata/services/youtube/gdata-youtube-service.h
@@ -133,6 +133,10 @@ void gdata_youtube_service_query_related_async (GDataYouTubeService *self, GData
 
 GDataYouTubeVideo *gdata_youtube_service_upload_video (GDataYouTubeService *self, GDataYouTubeVideo *video, GFile *video_file,
                                                        GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+void gdata_youtube_service_upload_video_async (GDataYouTubeService *self, GDataYouTubeVideo *video_entry, GFile *video_data,
+                                               GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+GDataYouTubeVideo *gdata_youtube_service_upload_video_finish (GDataYouTubeService *self, GAsyncResult *result,
+                                                              GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
 
 const gchar *gdata_youtube_service_get_developer_key (GDataYouTubeService *self) G_GNUC_PURE;
 const gchar *gdata_youtube_service_get_youtube_user (GDataYouTubeService *self) G_GNUC_PURE;
diff --git a/gdata/tests/youtube.c b/gdata/tests/youtube.c
index d86a852..d8082a1 100644
--- a/gdata/tests/youtube.c
+++ b/gdata/tests/youtube.c
@@ -320,6 +320,58 @@ test_upload_simple (gconstpointer service)
 }
 
 static void
+test_upload_async_cb (GDataService *service, GAsyncResult *async_result, GMainLoop *main_loop)
+{
+	GDataYouTubeVideo *new_video;
+	GError *error = NULL;
+
+	new_video = gdata_youtube_service_upload_video_finish (GDATA_YOUTUBE_SERVICE (service), async_result, &error);
+	g_assert_no_error (error);
+	g_assert (GDATA_IS_YOUTUBE_VIDEO (new_video));
+	g_clear_error (&error);
+
+	g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (new_video)), ==, "Bad Wedding Toast");
+
+	g_main_loop_quit (main_loop);
+	g_object_unref (new_video);
+}
+
+static void
+test_upload_async (gconstpointer service)
+{
+	GDataYouTubeVideo *video;
+	GDataMediaCategory *category;
+	GFile *video_file;
+	const gchar * const tags[] = { "toast", "wedding", NULL };
+	GMainLoop *main_loop;
+
+	main_loop = g_main_loop_new (NULL, TRUE);
+
+	video = gdata_youtube_video_new (NULL);
+
+	gdata_entry_set_title (GDATA_ENTRY (video), "Bad Wedding Toast");
+	gdata_youtube_video_set_description (video, "I gave a bad toast at my friend's wedding.");
+	category = gdata_media_category_new ("People", "http://gdata.youtube.com/schemas/2007/categories.cat";, NULL);
+	gdata_youtube_video_set_category (video, category);
+	g_object_unref (category);
+	gdata_youtube_video_set_keywords (video, tags);
+
+	/* TODO: fix the path */
+	video_file = g_file_new_for_path (TEST_FILE_DIR "sample.ogg");
+
+	/* Upload the video */
+	gdata_youtube_service_upload_video_async (GDATA_YOUTUBE_SERVICE (service), video, video_file, NULL,
+	                                          (GAsyncReadyCallback) test_upload_async_cb, main_loop);
+
+	g_object_unref (video);
+	g_object_unref (video_file);
+
+	g_main_loop_run (main_loop);
+	g_main_loop_unref (main_loop);
+}
+
+
+static void
 test_parsing_app_control (void)
 {
 	GDataYouTubeVideo *video;
@@ -1092,6 +1144,7 @@ main (int argc, char *argv[])
 		g_test_add_data_func ("/youtube/query/related_async", service, test_query_related_async);
 
 		g_test_add_data_func ("/youtube/upload/simple", service, test_upload_simple);
+		g_test_add_data_func ("/youtube/upload/async", service, test_upload_async);
 
 		g_test_add_data_func ("/youtube/query/single", service, test_query_single);
 		g_test_add_data_func ("/youtube/query/single_async", service, test_query_single_async);



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