[libgdata] Bug 600262 — Add async PicasaWeb upload API
- From: Richard Hans Schwarting <rschwart src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [libgdata] Bug 600262 — Add async PicasaWeb upload API
- Date: Fri, 11 Dec 2009 12:34:06 +0000 (UTC)
commit 79ab76ca03644618ec3d26c8d3e9642ba19f835e
Author: Richard Schwarting <rschwart src gnome org>
Date: Sat Dec 12 01:30:16 2009 +1300
Bug 600262 â?? Add async PicasaWeb upload API
Add an asynchronous file upload API to the PicasaWeb service. Closes: bgo#600262
docs/reference/gdata-sections.txt | 2 +
gdata/gdata.symbols | 2 +
gdata/services/picasaweb/gdata-picasaweb-service.c | 255 +++++++++++++++++---
gdata/services/picasaweb/gdata-picasaweb-service.h | 8 +-
gdata/tests/picasaweb.c | 100 ++++++++
5 files changed, 330 insertions(+), 37 deletions(-)
---
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index 60bc041..7f243a6 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -1188,6 +1188,8 @@ gdata_picasaweb_service_query_all_albums
gdata_picasaweb_service_query_all_albums_async
gdata_picasaweb_service_query_files
gdata_picasaweb_service_upload_file
+gdata_picasaweb_service_upload_file_async
+gdata_picasaweb_service_upload_file_finish
gdata_picasaweb_service_insert_album
<SUBSECTION Standard>
gdata_picasaweb_service_get_type
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index ed18558..d5cb93f 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -599,6 +599,8 @@ gdata_picasaweb_service_query_all_albums
gdata_picasaweb_service_query_all_albums_async
gdata_picasaweb_service_query_files
gdata_picasaweb_service_upload_file
+gdata_picasaweb_service_upload_file_async
+gdata_picasaweb_service_upload_file_finish
gdata_picasaweb_service_insert_album
gdata_picasaweb_feed_get_type
gdata_picasaweb_user_get_type
diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.c b/gdata/services/picasaweb/gdata-picasaweb-service.c
index 8a4131e..6dab5b4 100644
--- a/gdata/services/picasaweb/gdata-picasaweb-service.c
+++ b/gdata/services/picasaweb/gdata-picasaweb-service.c
@@ -295,6 +295,57 @@ gdata_picasaweb_service_query_files (GDataPicasaWebService *self, GDataPicasaWeb
progress_callback, progress_user_data, error);
}
+static GOutputStream *
+get_file_output_stream (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry, GFile *file_data, GError **error)
+{
+ GDataCategory *category;
+ GFileInfo *file_info = NULL;
+ const gchar *slug = NULL, *content_type = NULL, *user_id = NULL, *album_id = NULL;
+ GOutputStream *output_stream;
+ gchar *upload_uri;
+
+ /* Add the "photo" kind if the entry is missing it. If it already has the kind category, no duplicate is added. */
+ category = gdata_category_new ("http://schemas.google.com/photos/2007#photo", "http://schemas.google.com/g/2005#kind", NULL);
+ gdata_entry_add_category (GDATA_ENTRY (file_entry), category);
+ g_object_unref (category);
+
+ /* PicasaWeb allows you to post to a default Dropbox */
+ album_id = (album != NULL) ? gdata_entry_get_id (GDATA_ENTRY (album)) : "default";
+ user_id = gdata_service_get_username (GDATA_SERVICE (self));
+
+ file_info = g_file_query_info (file_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);
+
+ /* Build the upload URI and upload stream */
+ upload_uri = g_strdup_printf ("http://picasaweb.google.com/data/feed/api/user/%s/albumid/%s", user_id, album_id);
+ output_stream = gdata_upload_stream_new (GDATA_SERVICE (self), SOUP_METHOD_POST, upload_uri, GDATA_ENTRY (file_entry), slug, content_type);
+ g_free (upload_uri);
+ g_object_unref (file_info);
+
+ return output_stream;
+}
+
+static GDataPicasaWebFile *
+parse_spliced_stream (GOutputStream *output_stream, GError **error)
+{
+ const gchar *response_body;
+ gssize response_length;
+ GDataPicasaWebFile *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_PICASAWEB_FILE (gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_FILE, response_body, (gint) response_length, error));
+
+ return new_entry;
+}
+
/**
* gdata_picasaweb_service_upload_file:
* @self: a #GDataPicasaWebService
@@ -321,19 +372,17 @@ GDataPicasaWebFile *
gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry, GFile *file_data,
GCancellable *cancellable, GError **error)
{
- GDataPicasaWebFile *new_entry;
- GDataCategory *category;
GOutputStream *output_stream;
GInputStream *input_stream;
- const gchar *slug = NULL, *content_type = NULL, *response_body = NULL, *user_id = NULL, *album_id = NULL;
- gchar *upload_uri;
- gssize response_length;
- GFileInfo *file_info = NULL;
+ GDataPicasaWebFile *new_entry;
GError *child_error = NULL;
g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
+ g_return_val_if_fail (album == NULL || GDATA_IS_PICASAWEB_ALBUM (album), NULL);
g_return_val_if_fail (GDATA_IS_PICASAWEB_FILE (file_entry), NULL);
g_return_val_if_fail (G_IS_FILE (file_data), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
if (gdata_entry_is_inserted (GDATA_ENTRY (file_entry)) == TRUE) {
g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
@@ -347,28 +396,7 @@ gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWeb
return NULL;
}
- file_info = g_file_query_info (file_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);
-
- /* Add the "photo" kind if the entry is missing it. If it already has the kind category, no duplicate is added. */
- category = gdata_category_new ("http://schemas.google.com/photos/2007#photo", "http://schemas.google.com/g/2005#kind", NULL);
- gdata_entry_add_category (GDATA_ENTRY (file_entry), category);
- g_object_unref (category);
-
- /* PicasaWeb allows you to post to a default Dropbox */
- album_id = (album != NULL) ? gdata_entry_get_id (GDATA_ENTRY (album)) : "default";
- user_id = gdata_service_get_username (GDATA_SERVICE (self));
-
- /* Build the upload URI and upload stream */
- upload_uri = g_strdup_printf ("http://picasaweb.google.com/data/feed/api/user/%s/albumid/%s", user_id, album_id);
- output_stream = gdata_upload_stream_new (GDATA_SERVICE (self), SOUP_METHOD_POST, upload_uri, GDATA_ENTRY (file_entry), slug, content_type);
- g_free (upload_uri);
- g_object_unref (file_info);
-
+ output_stream = get_file_output_stream (self, album, file_entry, file_data, error);
if (output_stream == NULL)
return NULL;
@@ -384,24 +412,179 @@ gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWeb
g_object_unref (input_stream);
if (child_error != NULL) {
- /* Error! */
- g_propagate_error (error, child_error);
g_object_unref (output_stream);
+ g_propagate_error (error, child_error);
return NULL;
}
- /* 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_PICASAWEB_FILE (gdata_parsable_new_from_xml (GDATA_TYPE_PICASAWEB_FILE, response_body, (gint) response_length, error));
+ new_entry = parse_spliced_stream (output_stream, error);
g_object_unref (output_stream);
return new_entry;
}
/**
+ * gdata_picasaweb_service_upload_file_finish:
+ * @self: a #GDataPicasaWebService
+ * @result: a #GSimpleAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * This should be called to obtain the result of a call to
+ * gdata_picasaweb_service_upload_file_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 file 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: the inserted #GDataPicasaWebFile; unref with
+ * g_object_unref()
+ *
+ * Since: 0.6.0
+ */
+GDataPicasaWebFile *
+gdata_picasaweb_service_upload_file_finish (GDataPicasaWebService *self, GAsyncResult *result, GError **error)
+{
+ g_return_val_if_fail (GDATA_IS_PICASAWEB_SERVICE (self), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ /* propagate any potential errors we might have encountered in g_output_stream_splice() or gdata_picasaweb_service_upload_file_async() */
+ if (g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error))
+ return NULL;
+
+ g_assert (gdata_picasaweb_service_upload_file_async == g_simple_async_result_get_source_tag (G_SIMPLE_ASYNC_RESULT (result)));
+
+ return GDATA_PICASAWEB_FILE (g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result)));
+}
+
+typedef struct {
+ GDataPicasaWebService *service;
+ GAsyncReadyCallback callback;
+ gpointer user_data;
+} UploadFileAsyncData;
+
+static void
+upload_file_async_data_free (UploadFileAsyncData *data)
+{
+ g_object_unref (data->service);
+ g_slice_free (UploadFileAsyncData, data);
+}
+
+static void
+upload_file_async_cb (GOutputStream *output_stream, GAsyncResult *result, UploadFileAsyncData *data)
+{
+ GError *error = NULL;
+ GDataPicasaWebFile *file = 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)
+ file = parse_spliced_stream (output_stream, &error);
+
+ if (error == NULL && file != NULL)
+ async_result = g_simple_async_result_new (G_OBJECT (data->service), (GAsyncReadyCallback) data->callback,
+ data->user_data, gdata_picasaweb_service_upload_file_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, file, NULL);
+
+ g_simple_async_result_complete (async_result);
+
+ upload_file_async_data_free (data);
+}
+
+/**
+ * gdata_picasaweb_service_upload_file_async:
+ * @self: a #GDataPicasaWebService
+ * @album: a #GDataPicasaWebAlbum into which to insert the file, or %NULL
+ * @file_entry: a #GDataPicasaWebFile to insert
+ * @file_data: the actual file to upload
+ * @cancellable: optional #GCancellable object, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when authentication is finished
+ * @user_data: data to pass to the @callback function
+ *
+ * Uploads a file (photo or video) to the given PicasaWeb @album
+ * asynchronously, using the @actual_file from disk and the metadata
+ * from @file. If @album is %NULL, the file will be uploaded to the
+ * currently-authenticated user's "Drop Box" album. A user must be
+ * authenticated to use this function.
+ *
+ * @callback should call gdata_picasaweb_service_upload_file_finish()
+ * to obtain a #GDataPicasaWebFile representing the uploaded file and
+ * check for possible errors.
+ *
+ * Since: 0.6.0
+ **/
+void
+gdata_picasaweb_service_upload_file_async (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry,
+ GFile *file_data, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+ GOutputStream *output_stream;
+ GInputStream *input_stream;
+ UploadFileAsyncData *data;
+ GSimpleAsyncResult *result;
+ GError *error = NULL;
+
+ g_return_if_fail (GDATA_IS_PICASAWEB_SERVICE (self));
+ g_return_if_fail (album == NULL || GDATA_IS_PICASAWEB_ALBUM (album));
+ g_return_if_fail (GDATA_IS_PICASAWEB_FILE (file_entry));
+ g_return_if_fail (G_IS_FILE (file_data));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ if (gdata_entry_is_inserted (GDATA_ENTRY (file_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 file."));
+ goto error;
+ }
+
+ /* Prepare and retrieve a #GDataOutputStream for the file and its data */
+ output_stream = get_file_output_stream (self, album, file_entry, file_data, &error);
+ if (output_stream == NULL)
+ goto error;
+
+ /* Pipe the input file to the upload stream */
+ input_stream = G_INPUT_STREAM (g_file_read (file_data, cancellable, &error));
+ if (input_stream == NULL) {
+ g_object_unref (output_stream);
+ goto error;
+ }
+
+ data = g_slice_new (UploadFileAsyncData);
+ 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_file_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_picasaweb_service_insert_album:
* @self: a #GDataPicasaWebService
* @album: a #GDataPicasaWebAlbum to create on the server
diff --git a/gdata/services/picasaweb/gdata-picasaweb-service.h b/gdata/services/picasaweb/gdata-picasaweb-service.h
index 4e4a518..4ad06d7 100644
--- a/gdata/services/picasaweb/gdata-picasaweb-service.h
+++ b/gdata/services/picasaweb/gdata-picasaweb-service.h
@@ -83,7 +83,13 @@ GDataFeed *gdata_picasaweb_service_query_files (GDataPicasaWebService *self, GDa
GDataPicasaWebFile *gdata_picasaweb_service_upload_file (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry,
GFile *file_data, GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT;
-/* TODO: async version */
+
+void
+gdata_picasaweb_service_upload_file_async (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GDataPicasaWebFile *file_entry,
+ GFile *file_data, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+GDataPicasaWebFile *
+gdata_picasaweb_service_upload_file_finish (GDataPicasaWebService *self, GAsyncResult *result, GError **error) G_GNUC_WARN_UNUSED_RESULT;
+
GDataPicasaWebAlbum *gdata_picasaweb_service_insert_album (GDataPicasaWebService *self, GDataPicasaWebAlbum *album, GCancellable *cancellable,
GError **error) G_GNUC_WARN_UNUSED_RESULT;
diff --git a/gdata/tests/picasaweb.c b/gdata/tests/picasaweb.c
index 2656e8a..627c1ce 100644
--- a/gdata/tests/picasaweb.c
+++ b/gdata/tests/picasaweb.c
@@ -105,6 +105,103 @@ test_authentication_async (void)
}
static void
+test_upload_async_cb (GDataPicasaWebService *service, GAsyncResult *result, GMainLoop *main_loop)
+{
+ GDataPicasaWebFile *photo_new;
+ GError *error = NULL;
+
+ photo_new = gdata_picasaweb_service_upload_file_finish (service, result, &error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_PICASAWEB_FILE (photo_new));
+ g_clear_error (&error);
+ g_assert (gdata_entry_is_inserted (GDATA_ENTRY (photo_new)));
+
+ g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (photo_new)), ==, "Async Photo Entry Title");
+
+ g_main_loop_quit (main_loop);
+
+ g_object_unref (photo_new);
+}
+
+static void
+test_upload_async (GDataService *service)
+{
+ GDataPicasaWebFile *photo;
+ GFile *photo_file;
+ GTimeVal timeval;
+ gchar *xml, *time_str, *summary, *expected_xml, *parsed_time_str;
+ GRegex *regex;
+ GMatchInfo *match_info;
+ guint64 delta;
+ GMainLoop *main_loop = g_main_loop_new (NULL, TRUE);
+
+
+ g_get_current_time (&timeval);
+ time_str = g_time_val_to_iso8601 (&timeval);
+ summary = g_strdup_printf ("Async Photo Summary (%s)", time_str);
+
+ expected_xml = g_strdup_printf ("<entry "
+ "xmlns='http://www.w3.org/2005/Atom' "
+ "xmlns:gphoto='http://schemas.google.com/photos/2007' "
+ "xmlns:media='http://search.yahoo.com/mrss/' "
+ "xmlns:gd='http://schemas.google.com/g/2005' "
+ "xmlns:exif='http://schemas.google.com/photos/exif/2007' "
+ "xmlns:app='http://www.w3.org/2007/app' "
+ "xmlns:georss='http://www.georss.org/georss' "
+ "xmlns:gml='http://www.opengis.net/gml'>"
+ "<title type='text'>Async Photo Entry Title</title>"
+ "<summary type='text'>Async Photo Summary \\(%s\\)</summary>"
+ "<gphoto:position>0</gphoto:position>"
+ "<gphoto:timestamp>([0-9]+)</gphoto:timestamp>"
+ "<gphoto:commentingEnabled>true</gphoto:commentingEnabled>"
+ "<media:group>"
+ "<media:title type='plain'>Async Photo Entry Title</media:title>"
+ "<media:description type='plain'>Async Photo Summary \\(%s\\)</media:description>"
+ "</media:group>"
+ "</entry>", time_str, time_str);
+ g_free (time_str);
+
+ /* Build a regex to match the timestamp from the XML, since we can't definitely say what it'll be */
+ regex = g_regex_new (expected_xml, 0, 0, NULL);
+ g_free (expected_xml);
+
+ /* Build the photo */
+ photo = gdata_picasaweb_file_new (NULL);
+ gdata_entry_set_title (GDATA_ENTRY (photo), "Async Photo Entry Title");
+ gdata_picasaweb_file_set_caption (photo, summary);
+
+ /* Check the XML: match it against the regex built above, then check that the timestamp is within 100ms of the current time at the start of
+ * the test function. We can't check it exactly, as a few milliseconds may have passed inbetween building the expected_xml and building the XML
+ * for the photo. */
+ xml = gdata_parsable_get_xml (GDATA_PARSABLE (photo));
+ g_assert (g_regex_match (regex, xml, 0, &match_info) == TRUE);
+ parsed_time_str = g_match_info_fetch (match_info, 1);
+ delta = g_ascii_strtoull (parsed_time_str, NULL, 10) - (((guint64) timeval.tv_sec) * 1000 + ((guint64) timeval.tv_usec) / 1000);
+ g_assert_cmpuint (abs (delta), <, 100);
+
+ g_free (parsed_time_str);
+ g_free (xml);
+ g_regex_unref (regex);
+ g_match_info_free (match_info);
+
+ gdata_picasaweb_file_set_coordinates (photo, 17.127, -110.35);
+
+ /* File is public domain: http://en.wikipedia.org/wiki/File:German_garden_gnome_cropped.jpg */
+ photo_file = g_file_new_for_path (TEST_FILE_DIR "photo.jpg");
+
+ /* Upload the photo */
+ gdata_picasaweb_service_upload_file_async (GDATA_PICASAWEB_SERVICE (service), NULL, photo, photo_file, NULL,
+ (GAsyncReadyCallback) test_upload_async_cb, main_loop);
+
+ g_main_loop_run (main_loop);
+ g_main_loop_unref (main_loop);
+
+ g_free (summary);
+ g_object_unref (photo);
+ g_object_unref (photo_file);
+}
+
+static void
test_download_thumbnails (GDataService *service)
{
GDataFeed *album_feed, *photo_feed;
@@ -1132,6 +1229,9 @@ main (int argc, char *argv[])
g_test_add_func ("/picasaweb/authentication", test_authentication);
if (g_test_thorough () == TRUE)
g_test_add_func ("/picasaweb/authentication_async", test_authentication_async);
+ g_test_add_data_func ("/picasaweb/upload/photo", service, test_upload_simple);
+ if (g_test_thorough () == TRUE)
+ g_test_add_data_func ("/picasaweb/upload/photo_async", service, test_upload_async);
g_test_add_data_func ("/picasaweb/query/all_albums", service, test_query_all_albums);
g_test_add_data_func ("/picasaweb/query/user", service, test_query_user);
if (g_test_thorough () == TRUE)
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]