[libgdata] documents: Add support for resumable uploads and updates
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [libgdata] documents: Add support for resumable uploads and updates
- Date: Mon, 2 Apr 2012 14:33:29 +0000 (UTC)
commit 6e8499dfbdb6029b7797ae31957b87f688a88482
Author: Philip Withnall <philip tecnocode co uk>
Date: Mon Apr 2 15:12:35 2012 +0100
documents: Add support for resumable uploads and updates
This adds support for resumable uploads and updates to the Documents API. In
doing so, it was necessary to wade through a sea of bugs and documentation
inadequacies in the GData protocol (thanks, Google). In the end, it all seems
to work (ignoring the pile of hacks to handle differences between
non-resumable and resumable uploadsâ handling of metadata; and the bugs in
content-only uploads).
Itâs worth noting that this is only a basic implementation of resumable
upload/update. It does not, for example, automatically retry chunks of
uploads which fail. That can come later.
A new, expanded set of test cases for uploads/updates is included.
New API:
â gdata_documents_service_upload_document_resumable()
â gdata_documents_service_update_document_resumable()
Helps: https://bugzilla.gnome.org/show_bug.cgi?id=607272
Helps: https://bugzilla.gnome.org/show_bug.cgi?id=593537
docs/reference/gdata-sections.txt | 2 +
gdata/gdata-upload-stream.c | 26 +-
gdata/gdata.symbols | 2 +
gdata/services/documents/gdata-documents-service.c | 289 +++++++-
gdata/services/documents/gdata-documents-service.h | 8 +
gdata/tests/documents.c | 807 +++++++++++---------
6 files changed, 742 insertions(+), 392 deletions(-)
---
diff --git a/docs/reference/gdata-sections.txt b/docs/reference/gdata-sections.txt
index d671218..14f0f45 100644
--- a/docs/reference/gdata-sections.txt
+++ b/docs/reference/gdata-sections.txt
@@ -1750,7 +1750,9 @@ gdata_documents_service_get_spreadsheet_authorization_domain
gdata_documents_service_query_documents
gdata_documents_service_query_documents_async
gdata_documents_service_upload_document
+gdata_documents_service_upload_document_resumable
gdata_documents_service_update_document
+gdata_documents_service_update_document_resumable
gdata_documents_service_finish_upload
gdata_documents_service_add_entry_to_folder
gdata_documents_service_add_entry_to_folder_async
diff --git a/gdata/gdata-upload-stream.c b/gdata/gdata-upload-stream.c
index 04fe783..920712e 100644
--- a/gdata/gdata-upload-stream.c
+++ b/gdata/gdata-upload-stream.c
@@ -408,21 +408,11 @@ gdata_upload_stream_init (GDataUploadStream *self)
static SoupMessage *
build_message (GDataUploadStream *self, const gchar *method, const gchar *upload_uri)
{
- GDataUploadStreamPrivate *priv;
- GDataServiceClass *klass;
SoupMessage *new_message;
- priv = self->priv;
-
/* Build the message */
new_message = soup_message_new (method, upload_uri);
- /* Make sure the headers are set */
- klass = GDATA_SERVICE_GET_CLASS (priv->service);
- if (klass->append_query_headers != NULL) {
- klass->append_query_headers (priv->service, priv->authorization_domain, new_message);
- }
-
/* We don't want to accumulate chunks */
soup_message_body_set_accumulate (new_message->request_body, FALSE);
@@ -433,6 +423,7 @@ static GObject *
gdata_upload_stream_constructor (GType type, guint n_construct_params, GObjectConstructParam *construct_params)
{
GDataUploadStreamPrivate *priv;
+ GDataServiceClass *klass;
GObject *object;
/* Chain up to the parent class */
@@ -509,6 +500,13 @@ gdata_upload_stream_constructor (GType type, guint n_construct_params, GObjectCo
priv->chunk_size = MIN (priv->content_length, MAX_RESUMABLE_CHUNK_SIZE);
}
+ /* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around
+ * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */
+ klass = GDATA_SERVICE_GET_CLASS (priv->service);
+ if (klass->append_query_headers != NULL) {
+ klass->append_query_headers (priv->service, priv->authorization_domain, priv->message);
+ }
+
/* If the entry exists and has an ETag, we assume we're updating the entry, so we can set the If-Match header */
if (priv->entry != NULL && gdata_entry_get_etag (priv->entry) != NULL)
soup_message_headers_append (priv->message->request_headers, "If-Match", gdata_entry_get_etag (priv->entry));
@@ -1107,6 +1105,7 @@ upload_thread (GDataUploadStream *self)
g_assert (priv->cancellable != NULL);
while (TRUE) {
+ GDataServiceClass *klass;
gulong wrote_headers_signal, wrote_body_data_signal;
gchar *new_uri;
SoupMessage *new_message;
@@ -1200,6 +1199,13 @@ upload_thread (GDataUploadStream *self)
soup_message_headers_set_content_range (new_message->request_headers, priv->total_network_bytes_written,
priv->total_network_bytes_written + next_chunk_length - 1, priv->content_length);
+ /* Make sure the headers are set. HACK: This should actually be in build_message(), but we have to work around
+ * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 in GDataDocumentsService's append_query_headers(). */
+ klass = GDATA_SERVICE_GET_CLASS (priv->service);
+ if (klass->append_query_headers != NULL) {
+ klass->append_query_headers (priv->service, priv->authorization_domain, new_message);
+ }
+
g_signal_handler_disconnect (priv->message, wrote_body_data_signal);
g_signal_handler_disconnect (priv->message, wrote_headers_signal);
diff --git a/gdata/gdata.symbols b/gdata/gdata.symbols
index e75fa49..0bfbb3a 100644
--- a/gdata/gdata.symbols
+++ b/gdata/gdata.symbols
@@ -939,3 +939,5 @@ gdata_contacts_contact_get_file_as
gdata_contacts_contact_set_file_as
gdata_upload_stream_new_resumable
gdata_upload_stream_get_content_length
+gdata_documents_service_upload_document_resumable
+gdata_documents_service_update_document_resumable
diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c
index 1879186..919ad58 100644
--- a/gdata/services/documents/gdata-documents-service.c
+++ b/gdata/services/documents/gdata-documents-service.c
@@ -269,8 +269,11 @@ gdata_documents_service_error_quark (void)
return g_quark_from_static_string ("gdata-documents-service-error-quark");
}
+static void append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message);
static GList *get_authorization_domains (void);
+static gchar *_build_v2_upload_uri (GDataDocumentsFolder *folder) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
_GDATA_DEFINE_AUTHORIZATION_DOMAIN (documents, "writely", "https://docs.google.com/feeds/")
_GDATA_DEFINE_AUTHORIZATION_DOMAIN (spreadsheets, "wise", "https://spreadsheets.google.com/feeds/")
G_DEFINE_TYPE_WITH_CODE (GDataDocumentsService, gdata_documents_service, GDATA_TYPE_SERVICE, G_IMPLEMENT_INTERFACE (GDATA_TYPE_BATCHABLE, NULL))
@@ -280,6 +283,8 @@ gdata_documents_service_class_init (GDataDocumentsServiceClass *klass)
{
GDataServiceClass *service_class = GDATA_SERVICE_CLASS (klass);
service_class->feed_type = GDATA_TYPE_DOCUMENTS_FEED;
+
+ service_class->append_query_headers = append_query_headers;
service_class->get_authorization_domains = get_authorization_domains;
service_class->api_version = "3";
@@ -291,6 +296,49 @@ gdata_documents_service_init (GDataDocumentsService *self)
/* Nothing to see here */
}
+static void
+append_query_headers (GDataService *self, GDataAuthorizationDomain *domain, SoupMessage *message)
+{
+ g_assert (message != NULL);
+
+ if (message->method == SOUP_METHOD_POST && soup_message_headers_get_one (message->request_headers, "X-Upload-Content-Length") == NULL) {
+ gchar *upload_uri;
+ const gchar *v3_pos;
+
+ upload_uri = soup_uri_to_string (soup_message_get_uri (message), FALSE);
+ v3_pos = strstr (upload_uri, "://docs.google.com/feeds/upload/create-session/default/private/full");
+
+ if (v3_pos != NULL) {
+ gchar *v2_upload_uri;
+ SoupURI *_v2_upload_uri;
+
+ /* Content length header for resumable uploads. Only set it if this looks like the initial request of a resumable upload, and
+ * if no content length has been set previously.
+ * This allows methods like gdata_service_insert_entry() (which aren't resumable-upload-aware) to continue working for creating
+ * documents with metadata only, by simulating the initial request of a resumable upload as described here:
+ * https://developers.google.com/google-apps/documents-list/#creating_a_new_document_or_file_with_metadata_only */
+ soup_message_headers_replace (message->request_headers, "X-Upload-Content-Length", "0");
+
+ /* Also set the encoding to be content length encoding. */
+ soup_message_headers_set_encoding (message->request_headers, SOUP_ENCODING_CONTENT_LENGTH);
+
+ /* HACK: Work around http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 by changing the upload URI
+ * to the v2 API's upload URI. Grrr. */
+ v2_upload_uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/default/private/full",
+ v3_pos + strlen ("://docs.google.com/feeds/upload/create-session/default/private/full"), NULL);
+ _v2_upload_uri = soup_uri_new (v2_upload_uri);
+ soup_message_set_uri (message, _v2_upload_uri);
+ soup_uri_free (_v2_upload_uri);
+ g_free (v2_upload_uri);
+ }
+
+ g_free (upload_uri);
+ }
+
+ /* Chain up to the parent class */
+ GDATA_SERVICE_CLASS (gdata_documents_service_parent_class)->append_query_headers (self, domain, message);
+}
+
static GList *
get_authorization_domains (void)
{
@@ -302,6 +350,12 @@ get_authorization_domains (void)
return authorization_domains;
}
+static gchar *
+escape_for_uri (const gchar *unescaped_component)
+{
+ return g_uri_escape_string (unescaped_component, NULL, TRUE);
+}
+
/**
* gdata_documents_service_new:
* @authorizer: (allow-none): a #GDataAuthorizer to authorize the service's requests, or %NULL
@@ -470,9 +524,9 @@ gdata_documents_service_query_documents_async (GDataDocumentsService *self, GDat
static GDataUploadStream *
upload_update_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug, const gchar *content_type,
- const gchar *method, const gchar *upload_uri, GCancellable *cancellable)
+ goffset content_length, const gchar *method, const gchar *upload_uri, GCancellable *cancellable)
{
- /* Corrects a bug on spreadsheet content types handling
+ /* HACK: Corrects a bug on spreadsheet content types handling
* The content type for ODF spreadsheets is "application/vnd.oasis.opendocument.spreadsheet" for my ODF spreadsheet;
* but Google Documents' spreadsheet service is waiting for "application/x-vnd.oasis.opendocument.spreadsheet"
* and nothing else.
@@ -481,8 +535,35 @@ upload_update_document (GDataDocumentsService *self, GDataDocumentsDocument *doc
content_type = "application/x-vnd.oasis.opendocument.spreadsheet";
/* We need streaming file I/O: GDataUploadStream */
- return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_documents_authorization_domain (), method, upload_uri,
- GDATA_ENTRY (document), slug, content_type, cancellable));
+ if (content_length == -1) {
+ /* Non-resumable upload. */
+ return GDATA_UPLOAD_STREAM (gdata_upload_stream_new (GDATA_SERVICE (self), get_documents_authorization_domain (), method, upload_uri,
+ GDATA_ENTRY (document), slug, content_type, cancellable));
+ } else {
+ /* Resumable upload. */
+ return GDATA_UPLOAD_STREAM (gdata_upload_stream_new_resumable (GDATA_SERVICE (self), get_documents_authorization_domain (), method,
+ upload_uri, GDATA_ENTRY (document), slug, content_type, content_length,
+ cancellable));
+ }
+}
+
+static gboolean
+_upload_checks (GDataDocumentsService *self, GDataDocumentsDocument *document, GError **error)
+{
+ if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+ get_documents_authorization_domain ()) == FALSE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to upload documents."));
+ return FALSE;
+ }
+
+ if (document != NULL && gdata_entry_is_inserted (GDATA_ENTRY (document)) == TRUE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
+ _("The document has already been uploaded."));
+ return FALSE;
+ }
+
+ return TRUE;
}
/**
@@ -499,6 +580,10 @@ upload_update_document (GDataDocumentsService *self, GDataDocumentsDocument *doc
* the document data does not need to be provided at the moment, just the metadata, use gdata_service_insert_entry() instead (e.g. in the case of
* creating a new, empty file to be edited at a later date).
*
+ * This performs a non-resumable upload, unlike gdata_documents_service_upload_document(). This means that errors during transmission will cause the
+ * upload to fail, and the entire document will have to be re-uploaded. It is recommended that gdata_documents_service_upload_document_resumable()
+ * be used instead.
+ *
* If @document is %NULL, only the document data will be uploaded. The new document entry will be named using @slug, and will have default metadata.
*
* The stream returned by this function should be written to using the standard #GOutputStream methods, asychronously or synchronously. Once the stream
@@ -530,26 +615,94 @@ gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocum
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
- if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
- get_documents_authorization_domain ()) == FALSE) {
- g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
- _("You must be authenticated to upload documents."));
+ if (_upload_checks (self, document, error) == FALSE) {
return NULL;
}
- if (document != NULL && gdata_entry_is_inserted (GDATA_ENTRY (document)) == TRUE) {
- g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
- _("The document has already been uploaded."));
+ /* HACK: Since we're using non-resumable upload, we have to use the v2 API upload URI to work around
+ * http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 */
+ upload_uri = _build_v2_upload_uri (folder);
+ upload_stream = upload_update_document (self, document, slug, content_type, -1, SOUP_METHOD_POST, upload_uri, cancellable);
+ g_free (upload_uri);
+
+ return upload_stream;
+}
+
+/**
+ * gdata_documents_service_upload_document_resumable:
+ * @self: an authenticated #GDataDocumentsService
+ * @document: (allow-none): the #GDataDocumentsDocument to insert, or %NULL
+ * @slug: the filename to give to the uploaded document
+ * @content_type: the content type of the uploaded data
+ * @content_length: the size (in bytes) of the file being uploaded
+ * @folder: (allow-none): the folder to which the document should be uploaded, or %NULL
+ * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Uploads a document to Google Documents, using the properties from @document and the document data written to the resulting #GDataUploadStream. If
+ * the document data does not need to be provided at the moment, just the metadata, use gdata_service_insert_entry() instead (e.g. in the case of
+ * creating a new, empty file to be edited at a later date).
+ *
+ * Unlike gdata_documents_service_upload_document(), this method performs a
+ * <ulink type="http" url="http://code.google.com/apis/gdata/docs/resumable_upload.html">resumable upload</ulink> which allows for correction of
+ * transmission errors without re-uploading the entire file. Use of this method is preferred over gdata_documents_service_upload_document().
+ *
+ * If @document is %NULL, only the document data will be uploaded. The new document entry will be named using @slug, and will have default metadata.
+ *
+ * The stream returned by this function should be written to using the standard #GOutputStream methods, asychronously or synchronously. Once the stream
+ * is closed (using g_output_stream_close()), gdata_documents_service_finish_upload() should be called on it to parse and return the updated
+ * #GDataDocumentsDocument for the document. This must be done, as @document isn't updated in-place.
+ *
+ * In order to cancel the upload, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual
+ * #GOutputStream operations on the #GDataUploadStream will not cancel the entire upload; merely the write or close operation in question. See the
+ * #GDataUploadStream:cancellable for more details.
+ *
+ * Any upload errors will be thrown by the stream methods, and may come from the #GDataServiceError domain.
+ *
+ * Return value: (transfer full): a #GDataUploadStream to write the document data to, or %NULL; unref with g_object_unref()
+ *
+ * Since: 0.11.2
+ */
+GDataUploadStream *
+gdata_documents_service_upload_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug,
+ const gchar *content_type, goffset content_length, GDataDocumentsFolder *folder,
+ GCancellable *cancellable, GError **error)
+{
+ GDataUploadStream *upload_stream;
+ gchar *upload_uri;
+
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
+ g_return_val_if_fail (document == NULL || GDATA_IS_DOCUMENTS_DOCUMENT (document), NULL);
+ g_return_val_if_fail (slug != NULL && *slug != '\0', NULL);
+ g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL);
+ g_return_val_if_fail (folder == NULL || GDATA_IS_DOCUMENTS_FOLDER (folder), NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (_upload_checks (self, document, error) == FALSE) {
return NULL;
}
upload_uri = gdata_documents_service_get_upload_uri (folder);
- upload_stream = upload_update_document (self, document, slug, content_type, SOUP_METHOD_POST, upload_uri, cancellable);
+ upload_stream = upload_update_document (self, document, slug, content_type, content_length, SOUP_METHOD_POST, upload_uri, cancellable);
g_free (upload_uri);
return upload_stream;
}
+static gboolean
+_update_checks (GDataDocumentsService *self, GError **error)
+{
+ if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
+ get_documents_authorization_domain ()) == FALSE) {
+ g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
+ _("You must be authenticated to update documents."));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
/**
* gdata_documents_service_update_document:
* @self: a #GDataDocumentsService
@@ -562,6 +715,10 @@ gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocum
* Update the document using the properties from @document and the document data written to the resulting #GDataUploadStream. If the document data does
* not need to be changed, just the metadata, use gdata_service_update_entry() instead.
*
+ * This performs a non-resumable upload, unlike gdata_documents_service_update_document(). This means that errors during transmission will cause the
+ * upload to fail, and the entire document will have to be re-uploaded. It is recommended that gdata_documents_service_update_document_resumable()
+ * be used instead.
+ *
* The stream returned by this function should be written to using the standard #GOutputStream methods, asychronously or synchronously. Once the stream
* is closed (using g_output_stream_close()), gdata_documents_service_finish_upload() should be called on it to parse and return the updated
* #GDataDocumentsDocument for the document. This must be done, as @document isn't updated in-place.
@@ -591,17 +748,72 @@ gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocum
g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
- if (gdata_authorizer_is_authorized_for_domain (gdata_service_get_authorizer (GDATA_SERVICE (self)),
- get_documents_authorization_domain ()) == FALSE) {
- g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
- _("You must be authenticated to update documents."));
+ if (_update_checks (self, error) == FALSE) {
return NULL;
}
update_link = gdata_entry_look_up_link (GDATA_ENTRY (document), GDATA_LINK_EDIT_MEDIA);
g_assert (update_link != NULL);
- return upload_update_document (self, document, slug, content_type, SOUP_METHOD_PUT, gdata_link_get_uri (update_link), cancellable);
+ return upload_update_document (self, document, slug, content_type, -1, SOUP_METHOD_PUT, gdata_link_get_uri (update_link),
+ cancellable);
+}
+
+/**
+ * gdata_documents_service_update_document_resumable:
+ * @self: a #GDataDocumentsService
+ * @document: the #GDataDocumentsDocument to update
+ * @slug: the filename to give to the uploaded document
+ * @content_type: the content type of the uploaded data
+ * @content_length: the size (in bytes) of the file being uploaded
+ * @cancellable: (allow-none): a #GCancellable for the entire upload stream, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Update the document using the properties from @document and the document data written to the resulting #GDataUploadStream. If the document data does
+ * not need to be changed, just the metadata, use gdata_service_update_entry() instead.
+ *
+ * Unlike gdata_documents_service_update_document(), this method performs a
+ * <ulink type="http" url="http://code.google.com/apis/gdata/docs/resumable_upload.html">resumable upload</ulink> which allows for correction of
+ * transmission errors without re-uploading the entire file. Use of this method is preferred over gdata_documents_service_update_document().
+ *
+ * The stream returned by this function should be written to using the standard #GOutputStream methods, asychronously or synchronously. Once the stream
+ * is closed (using g_output_stream_close()), gdata_documents_service_finish_upload() should be called on it to parse and return the updated
+ * #GDataDocumentsDocument for the document. This must be done, as @document isn't updated in-place.
+ *
+ * In order to cancel the update, a #GCancellable passed in to @cancellable must be cancelled using g_cancellable_cancel(). Cancelling the individual
+ * #GOutputStream operations on the #GDataUploadStream will not cancel the entire update; merely the write or close operation in question. See the
+ * #GDataUploadStream:cancellable for more details.
+ *
+ * Any upload errors will be thrown by the stream methods, and may come from the #GDataServiceError domain.
+ *
+ * For more information, see gdata_service_update_entry().
+ *
+ * Return value: (transfer full): a #GDataUploadStream to write the document data to; unref with g_object_unref()
+ *
+ * Since: 0.11.2
+ */
+GDataUploadStream *
+gdata_documents_service_update_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug,
+ const gchar *content_type, goffset content_length, GCancellable *cancellable, GError **error)
+{
+ GDataLink *update_link;
+
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_SERVICE (self), NULL);
+ g_return_val_if_fail (GDATA_IS_DOCUMENTS_DOCUMENT (document), NULL);
+ g_return_val_if_fail (slug != NULL && *slug != '\0', NULL);
+ g_return_val_if_fail (content_type != NULL && *content_type != '\0', NULL);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
+
+ if (_update_checks (self, error) == FALSE) {
+ return NULL;
+ }
+
+ update_link = gdata_entry_look_up_link (GDATA_ENTRY (document), GDATA_LINK_RESUMABLE_EDIT_MEDIA);
+ g_assert (update_link != NULL);
+
+ return upload_update_document (self, document, slug, content_type, content_length, SOUP_METHOD_PUT, gdata_link_get_uri (update_link),
+ cancellable);
}
/**
@@ -1042,6 +1254,28 @@ gdata_documents_service_remove_entry_from_folder_finish (GDataDocumentsService *
g_assert_not_reached ();
}
+/* HACK: Work around http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=3033 by also using the upload URI for the v2 API. Grrr. */
+static gchar *
+_build_v2_upload_uri (GDataDocumentsFolder *folder)
+{
+ g_return_val_if_fail (folder == NULL || GDATA_IS_DOCUMENTS_FOLDER (folder), NULL);
+
+ /* If we have a folder, return the folder's upload URI */
+ if (folder != NULL) {
+ gchar *upload_uri, *escaped_resource_id;
+
+ escaped_resource_id = escape_for_uri (gdata_documents_entry_get_resource_id (GDATA_DOCUMENTS_ENTRY (folder)));
+ upload_uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/default/private/full/", escaped_resource_id,
+ "/contents", NULL);
+ g_free (escaped_resource_id);
+
+ return upload_uri;
+ }
+
+ /* Otherwise return the default upload URI */
+ return g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/default/private/full", NULL);
+}
+
/**
* gdata_documents_service_get_upload_uri:
* @folder: (allow-none): the #GDataDocumentsFolder into which to upload the document, or %NULL
@@ -1059,6 +1293,27 @@ gdata_documents_service_get_upload_uri (GDataDocumentsFolder *folder)
{
g_return_val_if_fail (folder == NULL || GDATA_IS_DOCUMENTS_FOLDER (folder), NULL);
+ if (folder != NULL) {
+ GDataLink *upload_link;
+
+ /* Get the folder's upload URI. */
+ upload_link = gdata_entry_look_up_link (GDATA_ENTRY (folder), GDATA_LINK_RESUMABLE_CREATE_MEDIA);
+
+ if (upload_link == NULL) {
+ gchar *upload_uri, *escaped_resource_id;
+
+ /* Fall back to building a URI manually. */
+ escaped_resource_id = escape_for_uri (gdata_documents_entry_get_resource_id (GDATA_DOCUMENTS_ENTRY (folder)));
+ upload_uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/upload/create-session/default/private/full/",
+ escaped_resource_id, "/contents", NULL);
+ g_free (escaped_resource_id);
+
+ return upload_uri;
+ }
+
+ return g_strdup (gdata_link_get_uri (upload_link));
+ }
+
/* Use resumable upload. */
return g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/upload/create-session/default/private/full", NULL);
}
diff --git a/gdata/services/documents/gdata-documents-service.h b/gdata/services/documents/gdata-documents-service.h
index 0a3a809..f68a6e0 100644
--- a/gdata/services/documents/gdata-documents-service.h
+++ b/gdata/services/documents/gdata-documents-service.h
@@ -99,9 +99,17 @@ void gdata_documents_service_query_documents_async (GDataDocumentsService *self,
GDataUploadStream *gdata_documents_service_upload_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug,
const gchar *content_type, GDataDocumentsFolder *folder,
GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataUploadStream *gdata_documents_service_upload_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug,
+ const gchar *content_type, goffset content_length, GDataDocumentsFolder *folder,
+ GCancellable *cancellable, GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
GDataUploadStream *gdata_documents_service_update_document (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug,
const gchar *content_type, GCancellable *cancellable,
GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+GDataUploadStream *gdata_documents_service_update_document_resumable (GDataDocumentsService *self, GDataDocumentsDocument *document, const gchar *slug,
+ const gchar *content_type, goffset content_length, GCancellable *cancellable,
+ GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
GDataDocumentsDocument *gdata_documents_service_finish_upload (GDataDocumentsService *self, GDataUploadStream *upload_stream,
GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
diff --git a/gdata/tests/documents.c b/gdata/tests/documents.c
index 7c57800..741baec 100644
--- a/gdata/tests/documents.c
+++ b/gdata/tests/documents.c
@@ -28,14 +28,19 @@
static gboolean
check_document_is_in_folder (GDataDocumentsDocument *document, GDataDocumentsFolder *folder)
{
- GList *categories;
+ GList *links;
gboolean found_folder_category = FALSE;
+ GDataLink *folder_self_link;
- for (categories = gdata_entry_get_categories (GDATA_ENTRY (document)); categories != NULL; categories = categories->next) {
- GDataCategory *category = GDATA_CATEGORY (categories->data);
+ folder_self_link = gdata_entry_look_up_link (GDATA_ENTRY (folder), GDATA_LINK_SELF);
+ g_assert (folder_self_link != NULL);
- if (strcmp (gdata_category_get_scheme (category), "http://schemas.google.com/docs/2007/folders/" DOCUMENTS_USERNAME) == 0 &&
- strcmp (gdata_category_get_term (category), gdata_entry_get_title (GDATA_ENTRY (folder))) == 0) {
+ for (links = gdata_entry_look_up_links (GDATA_ENTRY (document), "http://schemas.google.com/docs/2007#parent");
+ links != NULL; links = links->next) {
+ GDataLink *_link = GDATA_LINK (links->data);
+
+ if (strcmp (gdata_link_get_uri (_link), gdata_link_get_uri (folder_self_link)) == 0 &&
+ strcmp (gdata_link_get_title (_link), gdata_entry_get_title (GDATA_ENTRY (folder))) == 0) {
g_assert (found_folder_category == FALSE);
found_folder_category = TRUE;
}
@@ -44,6 +49,19 @@ check_document_is_in_folder (GDataDocumentsDocument *document, GDataDocumentsFol
return found_folder_category;
}
+static gboolean
+check_document_is_in_root_folder (GDataDocumentsDocument *document)
+{
+ GList *links;
+ gboolean is_in_root_folder;
+
+ links = gdata_entry_look_up_links (GDATA_ENTRY (document), "http://schemas.google.com/docs/2007#parent");
+ is_in_root_folder = (links == NULL) ? TRUE : FALSE;
+ g_list_free (links);
+
+ return is_in_root_folder;
+}
+
static void
delete_entry (GDataDocumentsEntry *entry, GDataService *service)
{
@@ -60,6 +78,27 @@ delete_entry (GDataDocumentsEntry *entry, GDataService *service)
g_object_unref (new_entry);
}
+static GDataDocumentsFolder *
+create_folder (GDataDocumentsService *service, const gchar *title)
+{
+ GDataDocumentsFolder *folder, *new_folder;
+ gchar *upload_uri;
+
+ folder = gdata_documents_folder_new (NULL);
+ gdata_entry_set_title (GDATA_ENTRY (folder), title);
+
+ /* Insert the folder */
+ upload_uri = gdata_documents_service_get_upload_uri (NULL);
+ new_folder = GDATA_DOCUMENTS_FOLDER (gdata_service_insert_entry (GDATA_SERVICE (service),
+ gdata_documents_service_get_primary_authorization_domain (),
+ upload_uri, GDATA_ENTRY (folder), NULL, NULL));
+ g_assert (GDATA_IS_DOCUMENTS_FOLDER (new_folder));
+ g_free (upload_uri);
+ g_object_unref (folder);
+
+ return new_folder;
+}
+
static void
test_authentication (void)
{
@@ -212,34 +251,6 @@ set_up_temp_document_spreadsheet (TempDocumentData *data, gconstpointer service)
}
static void
-set_up_temp_document_text (TempDocumentData *data, gconstpointer service)
-{
- GDataDocumentsText *document;
-
- /* Create a document */
- document = gdata_documents_text_new (NULL);
- gdata_entry_set_title (GDATA_ENTRY (document), "Temporary Document (Text)");
-
- data->document = _set_up_temp_document (GDATA_DOCUMENTS_ENTRY (document), GDATA_SERVICE (service));
-
- g_object_unref (document);
-}
-
-static void
-set_up_temp_document_presentation (TempDocumentData *data, gconstpointer service)
-{
- GDataDocumentsPresentation *document;
-
- /* Create a document */
- document = gdata_documents_presentation_new (NULL);
- gdata_entry_set_title (GDATA_ENTRY (document), "Temporary Document (Presentation)");
-
- data->document = _set_up_temp_document (GDATA_DOCUMENTS_ENTRY (document), GDATA_SERVICE (service));
-
- g_object_unref (document);
-}
-
-static void
tear_down_temp_document (TempDocumentData *data, gconstpointer service)
{
if (data->document != NULL) {
@@ -457,188 +468,400 @@ test_query_all_documents_async_progress_closure (TempDocumentsData *documents_da
g_slice_free (GDataAsyncProgressClosure, data);
}
+typedef enum {
+ UPLOAD_METADATA_ONLY,
+ UPLOAD_CONTENT_ONLY,
+ UPLOAD_CONTENT_AND_METADATA,
+} PayloadType;
+#define UPLOAD_PAYLOAD_TYPE_MAX UPLOAD_CONTENT_AND_METADATA
+
+const gchar *payload_type_names[] = {
+ "metadata-only",
+ "content-only",
+ "content-and-metadata",
+};
+
+typedef enum {
+ UPLOAD_IN_FOLDER,
+ UPLOAD_ROOT_FOLDER,
+} FolderType;
+#define UPLOAD_FOLDER_TYPE_MAX UPLOAD_ROOT_FOLDER
+
+const gchar *folder_type_names[] = {
+ "in-folder",
+ "root-folder",
+};
+
+typedef enum {
+ UPLOAD_RESUMABLE,
+ UPLOAD_NON_RESUMABLE,
+} ResumableType;
+#define UPLOAD_RESUMABLE_TYPE_MAX UPLOAD_NON_RESUMABLE
+
+const gchar *resumable_type_names[] = {
+ "resumable",
+ "non-resumable",
+};
+
+typedef struct {
+ PayloadType payload_type;
+ FolderType folder_type;
+ ResumableType resumable_type;
+ gchar *test_name;
+
+ GDataDocumentsService *service;
+} UploadDocumentTestParams;
+
typedef struct {
GDataDocumentsFolder *folder;
GDataDocumentsDocument *new_document;
} UploadDocumentData;
static void
-set_up_upload_document (UploadDocumentData *data, gconstpointer service)
-{
- data->folder = NULL;
- data->new_document = NULL;
-}
-
-static void
-set_up_upload_document_with_folder (UploadDocumentData *data, gconstpointer service)
+set_up_upload_document (UploadDocumentData *data, gconstpointer _test_params)
{
- GDataDocumentsFolder *folder;
- gchar *upload_uri;
+ const UploadDocumentTestParams *test_params = _test_params;
- /* Set up the structure */
- set_up_upload_document (data, service);
-
- /* Create a folder */
- folder = gdata_documents_folder_new (NULL);
- gdata_entry_set_title (GDATA_ENTRY (folder), "Temporary Folder for Uploading Documents");
+ data->new_document = NULL;
- /* Insert the folder */
- upload_uri = gdata_documents_service_get_upload_uri (NULL);
- data->folder = GDATA_DOCUMENTS_FOLDER (gdata_service_insert_entry (GDATA_SERVICE (service),
- gdata_documents_service_get_primary_authorization_domain (),
- upload_uri, GDATA_ENTRY (folder), NULL, NULL));
- g_assert (GDATA_IS_DOCUMENTS_FOLDER (data->folder));
- g_free (upload_uri);
- g_object_unref (folder);
+ switch (test_params->folder_type) {
+ case UPLOAD_IN_FOLDER:
+ data->folder = create_folder (test_params->service, "Temporary Folder for Uploading Documents");
+ break;
+ case UPLOAD_ROOT_FOLDER:
+ data->folder = NULL;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
}
static void
-tear_down_upload_document (UploadDocumentData *data, gconstpointer service)
+tear_down_upload_document (UploadDocumentData *data, gconstpointer _test_params)
{
+ const UploadDocumentTestParams *test_params = _test_params;
+
/* Delete the new file */
if (data->new_document != NULL) {
/* HACK: Query for the new document, as Google's servers appear to modify it behind our back if we don't upload both metadata and data
* when creating the document: http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=2337. We have to wait a few
* seconds before trying this to allow the various Google servers to catch up with each other. */
g_usleep (5 * G_USEC_PER_SEC);
- delete_entry (GDATA_DOCUMENTS_ENTRY (data->new_document), GDATA_SERVICE (service));
+ delete_entry (GDATA_DOCUMENTS_ENTRY (data->new_document), GDATA_SERVICE (test_params->service));
g_object_unref (data->new_document);
}
/* Delete the folder */
if (data->folder != NULL) {
- delete_entry (GDATA_DOCUMENTS_ENTRY (data->folder), GDATA_SERVICE (service));
+ delete_entry (GDATA_DOCUMENTS_ENTRY (data->folder), GDATA_SERVICE (test_params->service));
g_object_unref (data->folder);
}
}
static void
-test_upload_metadata (UploadDocumentData *data, gconstpointer service)
+test_upload (UploadDocumentData *data, gconstpointer _test_params)
{
- GDataDocumentsEntry *document;
+ const UploadDocumentTestParams *test_params = _test_params;
+
+ GDataDocumentsDocument *document = NULL;
+ GFile *document_file = NULL;
+ GFileInfo *file_info = NULL;
GError *error = NULL;
- gchar *upload_uri;
- document = GDATA_DOCUMENTS_ENTRY (gdata_documents_spreadsheet_new (NULL));
- gdata_entry_set_title (GDATA_ENTRY (document), "myNewSpreadsheet");
+ /* Upload content? */
+ switch (test_params->payload_type) {
+ case UPLOAD_METADATA_ONLY:
+ document_file = NULL;
+ file_info = NULL;
+ break;
+ case UPLOAD_CONTENT_ONLY:
+ case UPLOAD_CONTENT_AND_METADATA:
+ document_file = g_file_new_for_path (TEST_FILE_DIR "test.odt");
+ file_info = g_file_query_info (document_file,
+ G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE ","
+ G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE, NULL, &error);
+ g_assert_no_error (error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
- /* Insert the document */
- upload_uri = gdata_documents_service_get_upload_uri (NULL);
- data->new_document = GDATA_DOCUMENTS_DOCUMENT (gdata_service_insert_entry (GDATA_SERVICE (service),
- gdata_documents_service_get_primary_authorization_domain (),
- upload_uri, GDATA_ENTRY (document), NULL, &error));
- g_free (upload_uri);
- g_assert_no_error (error);
- g_assert (GDATA_IS_DOCUMENTS_SPREADSHEET (data->new_document));
+ /* Upload metadata? */
+ switch (test_params->payload_type) {
+ case UPLOAD_CONTENT_ONLY:
+ document = NULL;
+ break;
+ case UPLOAD_METADATA_ONLY:
+ case UPLOAD_CONTENT_AND_METADATA: {
+ gchar *title;
- g_clear_error (&error);
- g_object_unref (document);
-}
+ document = GDATA_DOCUMENTS_DOCUMENT (gdata_documents_text_new (NULL));
-static void
-test_upload_metadata_file (UploadDocumentData *data, gconstpointer service)
-{
- GDataDocumentsDocument *document;
- GFile *document_file;
- GFileInfo *file_info;
- GDataUploadStream *upload_stream;
- GFileInputStream *file_stream;
- GError *error = NULL;
+ /* Build a title including the test details. */
+ title = g_strdup_printf ("Test Upload file (%s)", test_params->test_name);
+ gdata_entry_set_title (GDATA_ENTRY (document), title);
+ g_free (title);
- document_file = g_file_new_for_path (TEST_FILE_DIR "test.odt");
- file_info = g_file_query_info (document_file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
- G_FILE_QUERY_INFO_NONE, NULL, &error);
- g_assert_no_error (error);
+ break;
+ }
+ default:
+ g_assert_not_reached ();
+ }
- document = GDATA_DOCUMENTS_DOCUMENT (gdata_documents_text_new (NULL));
- gdata_entry_set_title (GDATA_ENTRY (document), "upload_metadata_file");
+ if (test_params->payload_type == UPLOAD_METADATA_ONLY) {
+ gchar *upload_uri;
- /* Prepare the upload stream */
- upload_stream = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), document, g_file_info_get_display_name (file_info),
- g_file_info_get_content_type (file_info), NULL, NULL, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream));
+ /* Insert the document */
+ upload_uri = gdata_documents_service_get_upload_uri (data->folder);
+ data->new_document = GDATA_DOCUMENTS_DOCUMENT (gdata_service_insert_entry (GDATA_SERVICE (test_params->service),
+ gdata_documents_service_get_primary_authorization_domain (),
+ upload_uri, GDATA_ENTRY (document), NULL, &error));
+ g_free (upload_uri);
- g_object_unref (file_info);
+ g_assert_no_error (error);
+ } else {
+ GDataUploadStream *upload_stream;
+ GFileInputStream *file_stream;
+
+ /* Prepare the upload stream */
+ switch (test_params->resumable_type) {
+ case UPLOAD_NON_RESUMABLE:
+ upload_stream = gdata_documents_service_upload_document (test_params->service, document,
+ g_file_info_get_display_name (file_info),
+ g_file_info_get_content_type (file_info), data->folder,
+ NULL, &error);
+ break;
+ case UPLOAD_RESUMABLE:
+ upload_stream = gdata_documents_service_upload_document_resumable (test_params->service, document,
+ g_file_info_get_display_name (file_info),
+ g_file_info_get_content_type (file_info),
+ g_file_info_get_size (file_info), data->folder,
+ NULL, &error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
- /* Open the file */
- file_stream = g_file_read (document_file, NULL, &error);
- g_assert_no_error (error);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream));
- /* Upload the document */
- g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream),
- G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
- g_assert_no_error (error);
+ g_object_unref (file_info);
+
+ /* Open the file */
+ file_stream = g_file_read (document_file, NULL, &error);
+ g_assert_no_error (error);
+
+ /* Upload the document */
+ g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream),
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
+ g_assert_no_error (error);
+
+ /* Finish the upload */
+ data->new_document = gdata_documents_service_finish_upload (test_params->service, upload_stream, &error);
+ g_assert_no_error (error);
+
+ g_object_unref (upload_stream);
+ g_object_unref (file_stream);
+ }
- /* Finish the upload */
- data->new_document = gdata_documents_service_finish_upload (GDATA_DOCUMENTS_SERVICE (service), upload_stream, &error);
- g_assert_no_error (error);
g_assert (GDATA_IS_DOCUMENTS_TEXT (data->new_document));
/* Verify the uploaded document is the same as the original */
- g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (document)), ==, gdata_entry_get_title (GDATA_ENTRY (data->new_document)));
+ switch (test_params->payload_type) {
+ case UPLOAD_METADATA_ONLY:
+ case UPLOAD_CONTENT_AND_METADATA:
+ g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (data->new_document)), ==, gdata_entry_get_title (GDATA_ENTRY (document)));
+ break;
+ case UPLOAD_CONTENT_ONLY:
+ /* HACK: The title returned by the server varies depending on how we uploaded the document. */
+ if (test_params->resumable_type == UPLOAD_NON_RESUMABLE) {
+ g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (data->new_document)), ==, "test.odt");
+ } else {
+ g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (data->new_document)), ==, "Untitled");
+ }
+
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ /* Check it's in the right folder. */
+ switch (test_params->folder_type) {
+ case UPLOAD_IN_FOLDER:
+ /* HACK: When uploading content-only to a folder using the folder's resumable-create-media link, Google decides that it's
+ * not useful to list the folder in the returned entry XML for the new document (i.e. the server pretends the document's
+ * not in the folder you've just uploaded it to). Joy. */
+ g_assert (test_params->payload_type == UPLOAD_CONTENT_ONLY ||
+ check_document_is_in_folder (data->new_document, data->folder) == TRUE);
+ break;
+ case UPLOAD_ROOT_FOLDER:
+ /* Check root folder. */
+ g_assert (check_document_is_in_root_folder (data->new_document) == TRUE);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
g_clear_error (&error);
- g_object_unref (upload_stream);
- g_object_unref (file_stream);
- g_object_unref (document_file);
+ g_clear_object (&document_file);
+ g_clear_object (&document);
+}
+
+typedef struct {
+ PayloadType payload_type;
+ ResumableType resumable_type;
+ gchar *test_name;
+
+ GDataDocumentsService *service;
+} UpdateDocumentTestParams;
+
+typedef struct {
+ GDataDocumentsDocument *document;
+} UpdateDocumentData;
+
+static void
+set_up_update_document (UpdateDocumentData *data, gconstpointer _test_params)
+{
+ const UpdateDocumentTestParams *test_params = _test_params;
+ GDataDocumentsText *document;
+ gchar *title;
+
+ /* Create a document */
+ document = gdata_documents_text_new (NULL);
+ title = g_strdup_printf ("Test Update file (%s)", test_params->test_name);
+ gdata_entry_set_title (GDATA_ENTRY (document), title);
+ g_free (title);
+
+ data->document = _set_up_temp_document (GDATA_DOCUMENTS_ENTRY (document), GDATA_SERVICE (test_params->service));
+
g_object_unref (document);
}
static void
-test_upload_file_get_entry (UploadDocumentData *data, gconstpointer service)
+tear_down_update_document (UpdateDocumentData *data, gconstpointer _test_params)
{
- GDataEntry *new_presentation;
- GDataUploadStream *upload_stream;
- GFileInputStream *file_stream;
- GFile *document_file;
- GFileInfo *file_info;
+ const UpdateDocumentTestParams *test_params = _test_params;
+
+ /* Delete the new file */
+ if (data->document != NULL) {
+ /* HACK: Query for the new document, as Google's servers appear to modify it behind our back if we don't update both metadata and data
+ * when creating the document: http://code.google.com/a/google.com/p/apps-api-issues/issues/detail?id=2337. We have to wait a few
+ * seconds before trying this to allow the various Google servers to catch up with each other. */
+ g_usleep (5 * G_USEC_PER_SEC);
+ delete_entry (GDATA_DOCUMENTS_ENTRY (data->document), GDATA_SERVICE (test_params->service));
+ g_object_unref (data->document);
+ }
+}
+
+static void
+test_update (UpdateDocumentData *data, gconstpointer _test_params)
+{
+ const UpdateDocumentTestParams *test_params = _test_params;
+
+ GDataDocumentsDocument *updated_document;
+ gchar *original_title;
GError *error = NULL;
- document_file = g_file_new_for_path (TEST_FILE_DIR "test.ppt");
- file_info = g_file_query_info (document_file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
- G_FILE_QUERY_INFO_NONE, NULL, &error);
- g_assert_no_error (error);
+ switch (test_params->payload_type) {
+ case UPLOAD_METADATA_ONLY:
+ case UPLOAD_CONTENT_AND_METADATA: {
+ gchar *new_title;
- /* Prepare the upload stream */
- upload_stream = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), NULL, g_file_info_get_display_name (file_info),
- g_file_info_get_content_type (file_info), NULL, NULL, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream));
+ /* Change the title of the document */
+ original_title = g_strdup (gdata_entry_get_title (GDATA_ENTRY (data->document)));
+ new_title = g_strdup_printf ("Updated Test Update file (%s)", test_params->test_name);
+ gdata_entry_set_title (GDATA_ENTRY (data->document), new_title);
+ g_free (new_title);
- g_object_unref (file_info);
+ break;
+ }
+ case UPLOAD_CONTENT_ONLY:
+ original_title = NULL;
+ break;
+ default:
+ g_assert_not_reached ();
+ }
- /* Open the file */
- file_stream = g_file_read (document_file, NULL, &error);
- g_assert_no_error (error);
+ if (test_params->payload_type == UPLOAD_METADATA_ONLY) {
+ /* Update the document */
+ updated_document = GDATA_DOCUMENTS_DOCUMENT (gdata_service_update_entry (GDATA_SERVICE (test_params->service),
+ gdata_documents_service_get_primary_authorization_domain (),
+ GDATA_ENTRY (data->document), NULL, &error));
+ g_assert_no_error (error);
+ } else {
+ GDataUploadStream *upload_stream;
+ GFileInputStream *file_stream;
+ GFile *updated_document_file;
+ GFileInfo *file_info;
+
+ /* Prepare the updated file */
+ updated_document_file = g_file_new_for_path (TEST_FILE_DIR "test_updated.odt");
+
+ file_info = g_file_query_info (updated_document_file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","
+ G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE "," G_FILE_ATTRIBUTE_STANDARD_SIZE, G_FILE_QUERY_INFO_NONE,
+ NULL, &error);
+ g_assert_no_error (error);
+
+ /* Prepare the upload stream */
+ switch (test_params->resumable_type) {
+ case UPLOAD_NON_RESUMABLE:
+ upload_stream = gdata_documents_service_update_document (test_params->service, data->document,
+ g_file_info_get_display_name (file_info),
+ g_file_info_get_content_type (file_info),
+ NULL, &error);
+ break;
+ case UPLOAD_RESUMABLE:
+ upload_stream = gdata_documents_service_update_document_resumable (test_params->service, data->document,
+ g_file_info_get_display_name (file_info),
+ g_file_info_get_content_type (file_info),
+ g_file_info_get_size (file_info), NULL, &error);
+ break;
+ default:
+ g_assert_not_reached ();
+ }
- g_object_unref (document_file);
+ g_assert_no_error (error);
+ g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream));
- /* Upload the document */
- g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream),
- G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
- g_assert_no_error (error);
+ g_object_unref (file_info);
- /* Finish the upload */
- data->new_document = gdata_documents_service_finish_upload (GDATA_DOCUMENTS_SERVICE (service), upload_stream, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (data->new_document));
+ /* Open the file */
+ file_stream = g_file_read (updated_document_file, NULL, &error);
+ g_assert_no_error (error);
- g_object_unref (file_stream);
- g_object_unref (upload_stream);
+ g_object_unref (updated_document_file);
- /* Get the entry on the server */
- new_presentation = gdata_service_query_single_entry (GDATA_SERVICE (service), gdata_documents_service_get_primary_authorization_domain (),
- gdata_entry_get_id (GDATA_ENTRY (data->new_document)), NULL,
- GDATA_TYPE_DOCUMENTS_PRESENTATION, NULL, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (new_presentation));
+ /* Upload the updated document */
+ g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream),
+ G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
+ g_assert_no_error (error);
- /* Verify that the entry is correct (mangled version of the file's display name) */
- g_assert_cmpstr (gdata_entry_get_title (new_presentation), ==, "test");
+ /* Finish the upload */
+ updated_document = gdata_documents_service_finish_upload (test_params->service, upload_stream, &error);
+ g_assert_no_error (error);
- g_clear_error (&error);
- g_object_unref (new_presentation);
+ g_object_unref (upload_stream);
+ g_object_unref (file_stream);
+ }
+
+ g_assert (GDATA_IS_DOCUMENTS_TEXT (updated_document));
+
+ /* Check for success */
+ switch (test_params->payload_type) {
+ case UPLOAD_METADATA_ONLY:
+ case UPLOAD_CONTENT_AND_METADATA:
+ g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (updated_document)), !=, original_title);
+ /* Fall through */
+ case UPLOAD_CONTENT_ONLY:
+ g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (updated_document)), ==,
+ gdata_entry_get_title (GDATA_ENTRY (data->document)));
+ break;
+ default:
+ g_assert_not_reached ();
+ }
+
+ g_free (original_title);
+ g_object_unref (updated_document);
}
typedef struct {
@@ -844,206 +1067,6 @@ G_STMT_START {
} G_STMT_END);
static void
-test_upload_file_metadata_in_new_folder (UploadDocumentData *data, gconstpointer service)
-{
- GDataDocumentsDocument *document;
- GDataUploadStream *upload_stream;
- GFileInputStream *file_stream;
- GFile *document_file;
- GFileInfo *file_info;
- GError *error = NULL;
-
- /* Prepare the file */
- document_file = g_file_new_for_path (TEST_FILE_DIR "test.odt");
- document = GDATA_DOCUMENTS_DOCUMENT (gdata_documents_text_new (NULL));
- gdata_entry_set_title (GDATA_ENTRY (document), "upload_file_metadata_in_new_folder_text");
-
- file_info = g_file_query_info (document_file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
- G_FILE_QUERY_INFO_NONE, NULL, &error);
- g_assert_no_error (error);
-
- /* Prepare the upload stream */
- upload_stream = gdata_documents_service_upload_document (GDATA_DOCUMENTS_SERVICE (service), document, g_file_info_get_display_name (file_info),
- g_file_info_get_content_type (file_info), data->folder, NULL, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream));
-
- g_object_unref (file_info);
-
- /* Open the file */
- file_stream = g_file_read (document_file, NULL, &error);
- g_assert_no_error (error);
-
- g_object_unref (document_file);
-
- /* Upload the document into the new folder */
- g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream),
- G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
- g_assert_no_error (error);
-
- /* Finish the upload */
- data->new_document = gdata_documents_service_finish_upload (GDATA_DOCUMENTS_SERVICE (service), upload_stream, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_DOCUMENTS_TEXT (data->new_document));
-
- g_object_unref (upload_stream);
- g_object_unref (file_stream);
-
- /* Check for success */
- g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (data->new_document)), ==, gdata_entry_get_title (GDATA_ENTRY (document)));
- g_assert (check_document_is_in_folder (data->new_document, data->folder) == TRUE);
-
- g_clear_error (&error);
- g_object_unref (document);
-}
-
-static void
-test_update_metadata (TempDocumentData *data, gconstpointer service)
-{
- GDataDocumentsEntry *updated_document;
- gchar *original_title;
- GError *error = NULL;
-
- /* Change the document title */
- original_title = g_strdup (gdata_entry_get_title (GDATA_ENTRY (data->document)));
- gdata_entry_set_title (GDATA_ENTRY (data->document), "Updated Title for Metadata Only");
-
- /* Update the document */
- updated_document = GDATA_DOCUMENTS_ENTRY (gdata_service_update_entry (GDATA_SERVICE (service),
- gdata_documents_service_get_primary_authorization_domain (),
- GDATA_ENTRY (data->document), NULL, &error));
- g_assert_no_error (error);
- g_assert (GDATA_IS_DOCUMENTS_TEXT (updated_document));
- g_clear_error (&error);
-
- /* Check for success */
- g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (updated_document)), ==, gdata_entry_get_title (GDATA_ENTRY (data->document)));
- g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (updated_document)), !=, original_title);
-
- g_free (original_title);
- g_object_unref (updated_document);
-}
-
-static void
-test_update_metadata_file (TempDocumentData *data, gconstpointer service)
-{
- GDataDocumentsDocument *updated_document;
- GDataUploadStream *upload_stream;
- GFileInputStream *file_stream;
- GFile *updated_document_file;
- GFileInfo *file_info;
- gchar *original_title;
- GError *error = NULL;
-
- /* Change the title of the document */
- original_title = g_strdup (gdata_entry_get_title (GDATA_ENTRY (data->document)));
- gdata_entry_set_title (GDATA_ENTRY (data->document), "Updated Title for Metadata and File");
-
- /* Prepare the updated file */
- updated_document_file = g_file_new_for_path (TEST_FILE_DIR "test_updated.odt");
-
- file_info = g_file_query_info (updated_document_file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
- G_FILE_QUERY_INFO_NONE, NULL, &error);
- g_assert_no_error (error);
- g_clear_error (&error);
-
- /* Prepare the upload stream */
- upload_stream = gdata_documents_service_update_document (GDATA_DOCUMENTS_SERVICE (service), data->document,
- g_file_info_get_display_name (file_info), g_file_info_get_content_type (file_info),
- NULL, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream));
- g_clear_error (&error);
-
- g_object_unref (file_info);
-
- /* Open the file */
- file_stream = g_file_read (updated_document_file, NULL, &error);
- g_assert_no_error (error);
- g_clear_error (&error);
-
- g_object_unref (updated_document_file);
-
- /* Upload the updated document */
- g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream),
- G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
- g_assert_no_error (error);
- g_clear_error (&error);
-
- /* Finish the upload */
- updated_document = gdata_documents_service_finish_upload (GDATA_DOCUMENTS_SERVICE (service), upload_stream, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_DOCUMENTS_TEXT (updated_document));
- g_clear_error (&error);
-
- g_object_unref (upload_stream);
- g_object_unref (file_stream);
-
- /* Check for success */
- g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (updated_document)), ==, gdata_entry_get_title (GDATA_ENTRY (data->document)));
- g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (updated_document)), !=, original_title);
-
- g_free (original_title);
- g_object_unref (updated_document);
-}
-
-static void
-test_update_file (TempDocumentData *data, gconstpointer service)
-{
- GDataDocumentsDocument *updated_document;
- GDataUploadStream *upload_stream;
- GFileInputStream *file_stream;
- GFile *document_file;
- GFileInfo *file_info;
- GError *error = NULL;
-
- /* Get the file info for the updated document */
- document_file = g_file_new_for_path (TEST_FILE_DIR "test_updated_file.ppt");
- file_info = g_file_query_info (document_file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
- G_FILE_QUERY_INFO_NONE, NULL, &error);
- g_assert_no_error (error);
- g_clear_error (&error);
-
- /* Prepare the upload stream */
- upload_stream = gdata_documents_service_update_document (GDATA_DOCUMENTS_SERVICE (service), data->document,
- g_file_info_get_display_name (file_info), g_file_info_get_content_type (file_info),
- NULL, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_UPLOAD_STREAM (upload_stream));
- g_clear_error (&error);
-
- g_object_unref (file_info);
-
- /* Open the file */
- file_stream = g_file_read (document_file, NULL, &error);
- g_assert_no_error (error);
- g_clear_error (&error);
-
- g_object_unref (document_file);
-
- /* Upload the document */
- g_output_stream_splice (G_OUTPUT_STREAM (upload_stream), G_INPUT_STREAM (file_stream),
- G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET, NULL, &error);
- g_assert_no_error (error);
- g_clear_error (&error);
-
- g_object_unref (file_stream);
-
- /* Finish the upload */
- updated_document = gdata_documents_service_finish_upload (GDATA_DOCUMENTS_SERVICE (service), upload_stream, &error);
- g_assert_no_error (error);
- g_assert (GDATA_IS_DOCUMENTS_PRESENTATION (updated_document));
- g_clear_error (&error);
-
- g_object_unref (upload_stream);
-
- /* Check for success */
- g_assert_cmpstr (gdata_entry_get_title (GDATA_ENTRY (updated_document)), ==, gdata_entry_get_title (GDATA_ENTRY (data->document)));
-
- g_object_unref (updated_document);
-}
-
-static void
_test_download_document (GDataDocumentsDocument *document, GDataService *service)
{
GDataDownloadStream *download_stream;
@@ -1458,24 +1481,78 @@ main (int argc, char *argv[])
tear_down_temp_document);
g_test_add ("/documents/delete/folder", TempFolderData, service, set_up_temp_folder, test_delete_folder, tear_down_temp_folder);
- g_test_add ("/documents/upload/only_file_get_entry", UploadDocumentData, service, set_up_upload_document, test_upload_file_get_entry,
- tear_down_upload_document);
- g_test_add ("/documents/upload/metadata_file", UploadDocumentData, service, set_up_upload_document, test_upload_metadata_file,
- tear_down_upload_document);
- g_test_add ("/documents/upload/only_metadata", UploadDocumentData, service, set_up_upload_document, test_upload_metadata,
- tear_down_upload_document);
- g_test_add ("/documents/upload/metadata_file_in_new_folder", UploadDocumentData, service, set_up_upload_document_with_folder,
- test_upload_file_metadata_in_new_folder, tear_down_upload_document);
+ /* Test all possible combinations of conditions for resumable uploads. */
+ {
+ PayloadType i;
+ FolderType j;
+ ResumableType k;
+
+ for (i = 0; i < UPLOAD_PAYLOAD_TYPE_MAX + 1; i++) {
+ for (j = 0; j < UPLOAD_FOLDER_TYPE_MAX + 1; j++) {
+ for (k = 0; k < UPLOAD_RESUMABLE_TYPE_MAX + 1; k++) {
+ UploadDocumentTestParams *test_params;
+ gchar *test_name;
+
+ /* Resumable metadata-only uploads don't make sense. */
+ if (i == UPLOAD_METADATA_ONLY && k == UPLOAD_RESUMABLE) {
+ continue;
+ }
+
+ test_name = g_strdup_printf ("/documents/upload/%s/%s/%s",
+ payload_type_names[i], folder_type_names[j],
+ resumable_type_names[k]);
+
+ /* Allocate a new struct. We leak this. */
+ test_params = g_slice_new0 (UploadDocumentTestParams);
+ test_params->payload_type = i;
+ test_params->folder_type = j;
+ test_params->resumable_type = k;
+ test_params->test_name = g_strdup (test_name);
+ test_params->service = GDATA_DOCUMENTS_SERVICE (service);
+
+ g_test_add (test_name, UploadDocumentData, test_params, set_up_upload_document, test_upload,
+ tear_down_upload_document);
+
+ g_free (test_name);
+ }
+ }
+ }
+ }
g_test_add ("/documents/download/document", TempDocumentsData, service, set_up_temp_documents, test_download_document,
tear_down_temp_documents);
- g_test_add ("/documents/update/only_metadata", TempDocumentData, service, set_up_temp_document_text, test_update_metadata,
- tear_down_temp_document);
- g_test_add ("/documents/update/only_file", TempDocumentData, service, set_up_temp_document_presentation, test_update_file,
- tear_down_temp_document);
- g_test_add ("/documents/update/metadata_file", TempDocumentData, service, set_up_temp_document_text, test_update_metadata_file,
- tear_down_temp_document);
+ /* Test all possible combinations of conditions for resumable updates. */
+ {
+ PayloadType i;
+ ResumableType j;
+
+ for (i = 0; i < UPLOAD_PAYLOAD_TYPE_MAX + 1; i++) {
+ for (j = 0; j < UPLOAD_RESUMABLE_TYPE_MAX + 1; j++) {
+ UpdateDocumentTestParams *test_params;
+ gchar *test_name;
+
+ /* Resumable metadata-only updates don't make sense. */
+ if (i == UPLOAD_METADATA_ONLY && j == UPLOAD_RESUMABLE) {
+ continue;
+ }
+
+ test_name = g_strdup_printf ("/documents/update/%s/%s", payload_type_names[i], resumable_type_names[j]);
+
+ /* Allocate a new struct. We leak this. */
+ test_params = g_slice_new0 (UpdateDocumentTestParams);
+ test_params->payload_type = i;
+ test_params->resumable_type = j;
+ test_params->test_name = g_strdup (test_name);
+ test_params->service = GDATA_DOCUMENTS_SERVICE (service);
+
+ g_test_add (test_name, UpdateDocumentData, test_params, set_up_update_document, test_update,
+ tear_down_update_document);
+
+ g_free (test_name);
+ }
+ }
+ }
g_test_add ("/documents/access-rule/insert", TempDocumentData, service, set_up_temp_document_spreadsheet, test_access_rule_insert,
tear_down_temp_document);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]