[libgdata] [core] Improve cancellation support



commit b56ed5d7d6bdebc793bbb5a13d022aa61fb86860
Author: Philip Withnall <philip tecnocode co uk>
Date:   Fri Apr 9 00:53:36 2010 +0100

    [core] Improve cancellation support
    
    Make use of libsoup's message cancellation functionality, rather than
    checking for cancellation ourselves at arbitrary points during operations.
    
    This fixes cancellation support for sync and async operations, and adds an
    API guarantee that if an operation is cancelled before its network activity
    has finished (or started), the operation will always return a cancelled
    error. If an operation is cancelled after its network activity has finished,
    the operation will always return its results as normal.

 docs/reference/gdata-overview.xml                  |   26 +++-
 gdata/gdata-access-handler.c                       |  130 ++++--------
 gdata/gdata-private.h                              |    3 +-
 gdata/gdata-service.c                              |  227 +++++++++-----------
 gdata/services/contacts/gdata-contacts-contact.c   |   66 ++----
 gdata/services/documents/gdata-documents-service.c |   72 ++-----
 6 files changed, 211 insertions(+), 313 deletions(-)
---
diff --git a/docs/reference/gdata-overview.xml b/docs/reference/gdata-overview.xml
index 93fdff0..f9a9bb8 100644
--- a/docs/reference/gdata-overview.xml
+++ b/docs/reference/gdata-overview.xml
@@ -99,7 +99,7 @@
 		<para>As the GData protocol (and all the service-specific protocols which extend it) is reasonably young, it is subject to fairly
 			frequent updates and expansions. While backwards compatibility is maintained, these updates necessitate that libgdata
 			remains fairly flexible in how it treats data. The sections below detail some of the ways in which libgdata achieves this,
-			and the reasoning behind them.</para>
+			and the reasoning behind them, as well as other major design decisions behind libgdata's API.</para>
 
 		<refsect2 id="enumerable-properties">
 			<title>Enumerable Properties</title>
@@ -143,5 +143,29 @@
 				implement support for a new GData service, it is for the benefit of everyone if this implementation is done in libgdata
 				itself, rather than their application.</para>
 		</refsect2>
+
+		<refsect2>
+			<title>Cancellable Support</title>
+			<para>As libgdata is a network library, it has to be able to deal with operations which take a long (and indeterminate) amount
+				of time due to network latencies. As well as providing asynchronous operation support, every such operation in libgdata
+				is cancellable, using <type><link linkend="GCancellable">GCancellable</link></type>.</para>
+			<para>Using <type><link linkend="GCancellable">GCancellable</link></type>, any ongoing libgdata operation can be cancelled
+				from any other thread by calling <function><link linkend="g-cancellable-cancel">g_cancellable_cancel</link></function>.
+				If the ongoing operation is doing network activity, the operation will be cancelled as safely as possible (although
+				the server's state cannot be guaranteed when cancelling a non-idempotent operation, such as an insertion or update,
+				since the server may have already committed the results of the operation, but might not have returned them to libgdata
+				yet) and the operation will return to its calling function with a
+				<link linkend="G-IO-ERROR-CANCELLED:CAPS"><code class="literal">G_IO_ERROR_CANCELLED</code></link> error. Similarly,
+				if the operation is yet to do network activity, it will return with the above error before the network activity is
+				started, leaving the server unchanged.</para>
+			<para>However, if the operation has finished its network activity, libgdata does not guarantee that it will return with an
+				error â?? it may return successfully. There is no way to fix this, as it is an inherent race condition between checking
+				for cancellation for the last time, and returning the successful result. Rather than reduce the probability of the race
+				condition occurring, but still have the possibility of it occurring, libgdata will just continue to process an operation
+				after its network activity is over, and return success.</para>
+			<para>This may be useful in situations where the user is cancelling an operation due to it taking too long; the application
+				using libgdata may want to make use of the result of the operation, even if it has previously tried to cancel the
+				operation after network activity finished.</para>
+		</refsect2>
 	</refsect1>
 </refentry>
diff --git a/gdata/gdata-access-handler.c b/gdata/gdata-access-handler.c
index 31e5069..aec2e5e 100644
--- a/gdata/gdata-access-handler.c
+++ b/gdata/gdata-access-handler.c
@@ -81,10 +81,9 @@ gdata_access_handler_get_type (void)
  * Since: 0.3.0
  **/
 GDataFeed *
-gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service, GCancellable *cancellable, GDataQueryProgressCallback progress_callback,
-				gpointer progress_user_data, GError **error)
+gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service, GCancellable *cancellable,
+                                GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataFeed *feed;
 	GDataLink *link;
 	SoupMessage *message;
@@ -93,32 +92,25 @@ gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service,
 	/* TODO: async version */
 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
 	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Get the ACL URI */
+	/* TODO: ETag support */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/acl/2007#accessControlList";);
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_GET, gdata_link_get_uri (link));
-
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (service);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (service, message);
+	message = _gdata_service_build_message (service, SOUP_METHOD_GET, gdata_link_get_uri (link), NULL, FALSE);
 
 	/* Send the message */
-	status = _gdata_service_send_message (service, message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	status = _gdata_service_send_message (service, message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return NULL;
-	}
-
-	if (status != SOUP_STATUS_OK) {
+	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (service);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (service, GDATA_OPERATION_QUERY, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
@@ -127,7 +119,6 @@ gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service,
 	}
 
 	g_assert (message->response_body->data != NULL);
-
 	feed = _gdata_feed_new_from_xml (GDATA_TYPE_FEED, message->response_body->data, message->response_body->length, GDATA_TYPE_ACCESS_RULE,
 	                                 progress_callback, progress_user_data, error);
 	g_object_unref (message);
@@ -162,7 +153,6 @@ gdata_access_handler_get_rules (GDataAccessHandler *self, GDataService *service,
 GDataAccessRule *
 gdata_access_handler_insert_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule, GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataAccessRule *updated_rule;
 	GDataLink *link;
 	SoupMessage *message;
@@ -172,6 +162,8 @@ gdata_access_handler_insert_rule (GDataAccessHandler *self, GDataService *servic
 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
 	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
 	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (rule), NULL);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	if (gdata_entry_is_inserted (GDATA_ENTRY (rule)) == TRUE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_ENTRY_ALREADY_INSERTED,
@@ -180,34 +172,25 @@ gdata_access_handler_insert_rule (GDataAccessHandler *self, GDataService *servic
 	}
 
 	/* Get the ACL URI */
+	/* TODO: ETag support */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/acl/2007#accessControlList";);
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_POST, gdata_link_get_uri (link));
-
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (service);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (service, message);
+	message = _gdata_service_build_message (service, SOUP_METHOD_POST, gdata_link_get_uri (link), NULL, FALSE);
 
 	/* Append the data */
 	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (rule));
 	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
 
 	/* Send the message */
-	status = _gdata_service_send_message (service, message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	status = _gdata_service_send_message (service, message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return NULL;
-	}
-
-	if (status != SOUP_STATUS_CREATED) {
+	} else if (status != SOUP_STATUS_CREATED) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (service);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (service, GDATA_OPERATION_INSERTION, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
@@ -215,10 +198,8 @@ gdata_access_handler_insert_rule (GDataAccessHandler *self, GDataService *servic
 		return NULL;
 	}
 
-	/* Build the updated entry */
-	g_assert (message->response_body->data != NULL);
-
 	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
+	g_assert (message->response_body->data != NULL);
 	updated_rule = GDATA_ACCESS_RULE (gdata_parsable_new_from_xml (G_OBJECT_TYPE (rule), message->response_body->data,
 								       message->response_body->length, error));
 	g_object_unref (message);
@@ -227,7 +208,7 @@ gdata_access_handler_insert_rule (GDataAccessHandler *self, GDataService *servic
 }
 
 static SoupMessage *
-get_soup_message (GDataAccessHandler *access_handler, GDataAccessRule *rule, const gchar *method)
+build_message (GDataAccessHandler *access_handler, GDataService *service, GDataAccessRule *rule, const gchar *method)
 {
 	GDataLink *link;
 	SoupMessage *message;
@@ -238,7 +219,7 @@ get_soup_message (GDataAccessHandler *access_handler, GDataAccessRule *rule, con
 	/* Get the edit URI */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (rule), GDATA_LINK_EDIT);
 	if (link != NULL)
-		return soup_message_new (method, gdata_link_get_uri (link));
+		return _gdata_service_build_message (service, method, gdata_link_get_uri (link), NULL, FALSE);
 
 	/* Try building the URI instead */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (access_handler), "http://schemas.google.com/acl/2007#accessControlList";);
@@ -254,7 +235,7 @@ get_soup_message (GDataAccessHandler *access_handler, GDataAccessRule *rule, con
 	}
 
 	uri = g_string_free (uri_string, FALSE);
-	message = soup_message_new (method, uri);
+	message = _gdata_service_build_message (service, method, uri, NULL, FALSE);
 	g_free (uri);
 
 	return message;
@@ -284,7 +265,6 @@ get_soup_message (GDataAccessHandler *access_handler, GDataAccessRule *rule, con
 GDataAccessRule *
 gdata_access_handler_update_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule, GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataAccessRule *updated_rule;
 	SoupMessage *message;
 	gchar *upload_data;
@@ -293,35 +273,26 @@ gdata_access_handler_update_rule (GDataAccessHandler *self, GDataService *servic
 	g_return_val_if_fail (GDATA_IS_ENTRY (self), NULL);
 	g_return_val_if_fail (GDATA_IS_SERVICE (service), NULL);
 	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (rule), NULL);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
-	message = get_soup_message (self, rule, SOUP_METHOD_PUT);
-
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (service);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (service, message);
-
-	/* Looks like ACLs don't support ETags */
+	/* TODO: ETag support */
+	message = build_message (self, service, rule, SOUP_METHOD_PUT);
 
 	/* Append the data */
 	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (rule));
 	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
 
 	/* Send the message */
-	status = _gdata_service_send_message (service, message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	status = _gdata_service_send_message (service, message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return NULL;
-	}
-
-	if (status != SOUP_STATUS_OK) {
+	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (service);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (service, GDATA_OPERATION_UPDATE, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
@@ -329,10 +300,8 @@ gdata_access_handler_update_rule (GDataAccessHandler *self, GDataService *servic
 		return NULL;
 	}
 
-	/* Build the updated entry */
-	g_assert (message->response_body->data != NULL);
-
 	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
+	g_assert (message->response_body->data != NULL);
 	updated_rule = GDATA_ACCESS_RULE (gdata_parsable_new_from_xml (G_OBJECT_TYPE (rule), message->response_body->data,
 								       message->response_body->length, error));
 	g_object_unref (message);
@@ -363,7 +332,6 @@ gdata_access_handler_update_rule (GDataAccessHandler *self, GDataService *servic
 gboolean
 gdata_access_handler_delete_rule (GDataAccessHandler *self, GDataService *service, GDataAccessRule *rule, GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataAccessHandlerIface *iface;
 	SoupMessage *message;
 	guint status;
@@ -371,6 +339,8 @@ gdata_access_handler_delete_rule (GDataAccessHandler *self, GDataService *servic
 	g_return_val_if_fail (GDATA_IS_ENTRY (self), FALSE);
 	g_return_val_if_fail (GDATA_IS_SERVICE (service), FALSE);
 	g_return_val_if_fail (GDATA_IS_ACCESS_RULE (rule), FALSE);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
 	/* The owner of the access handler can't be deleted */
 	iface = GDATA_ACCESS_HANDLER_GET_IFACE (self);
@@ -381,30 +351,18 @@ gdata_access_handler_delete_rule (GDataAccessHandler *self, GDataService *servic
 		return FALSE;
 	}
 
-	message = get_soup_message (self, rule, SOUP_METHOD_DELETE);
-
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (service);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (service, message);
+	/* TODO: ETag support */
+	/* Build and send the message */
+	message = build_message (self, service, rule, SOUP_METHOD_DELETE);
+	status = _gdata_service_send_message (service, message, cancellable, error);
 
-	/* Looks like ACLs don't support ETags */
-
-	/* Send the message */
-	status = _gdata_service_send_message (service, message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return FALSE;
-	}
-
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return FALSE;
-	}
-
-	if (status != SOUP_STATUS_OK) {
+	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (service);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (service, GDATA_OPERATION_DELETION, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
diff --git a/gdata/gdata-private.h b/gdata/gdata-private.h
index d67a536..4483bd7 100644
--- a/gdata/gdata-private.h
+++ b/gdata/gdata-private.h
@@ -47,7 +47,8 @@ typedef enum {
 #include "gdata-service.h"
 SoupSession *_gdata_service_get_session (GDataService *self);
 void _gdata_service_set_authenticated (GDataService *self, gboolean authenticated);
-guint _gdata_service_send_message (GDataService *self, SoupMessage *message, GError **error);
+SoupMessage *_gdata_service_build_message (GDataService *self, const gchar *method, const gchar *uri, const gchar *etag, gboolean etag_if_match);
+guint _gdata_service_send_message (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error);
 SoupMessage *_gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *query, GCancellable *cancellable,
 				   GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
 				   GError **error) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
diff --git a/gdata/gdata-service.c b/gdata/gdata-service.c
index 2031a13..5b60e51 100644
--- a/gdata/gdata-service.c
+++ b/gdata/gdata-service.c
@@ -627,6 +627,7 @@ authenticate (GDataService *self, const gchar *username, const gchar *password,
 
 	/* Build the message */
 	message = soup_message_new (SOUP_METHOD_POST, klass->authentication_uri);
+	g_object_set_data_full (G_OBJECT (message), "session", g_object_ref (self->priv->session), (GDestroyNotify) g_object_unref);
 	soup_message_set_request (message, "application/x-www-form-urlencoded", SOUP_MEMORY_TAKE, request_body, strlen (request_body));
 
 	/* Send the message */
@@ -848,8 +849,38 @@ gdata_service_authenticate (GDataService *self, const gchar *username, const gch
 	return authenticate (self, username, password, NULL, NULL, cancellable, error);
 }
 
+SoupMessage *
+_gdata_service_build_message (GDataService *self, const gchar *method, const gchar *uri, const gchar *etag, gboolean etag_if_match)
+{
+	SoupMessage *message;
+	GDataServiceClass *klass;
+
+	/* Create the message and store a pointer to the session in it,
+	 * so we can cancel the message in message_cancel_cb() from _gdata_service_send_message() */
+	message = soup_message_new (method, uri);
+	g_object_set_data_full (G_OBJECT (message), "session", g_object_ref (self->priv->session), (GDestroyNotify) g_object_unref);
+
+	/* Make sure subclasses set their headers */
+	klass = GDATA_SERVICE_GET_CLASS (self);
+	if (klass->append_query_headers != NULL)
+		klass->append_query_headers (self, message);
+
+	/* Append the ETag header if possible */
+	if (etag != NULL)
+		soup_message_headers_append (message->request_headers, (etag_if_match == TRUE) ? "If-Match" : "If-None-Match", etag);
+
+	return message;
+}
+
+static void
+message_cancel_cb (GCancellable *cancellable, SoupMessage *message)
+{
+	if (message != NULL)
+		soup_session_cancel_message (g_object_get_data (G_OBJECT (message), "session"), message, SOUP_STATUS_CANCELLED);
+}
+
 guint
-_gdata_service_send_message (GDataService *self, SoupMessage *message, GError **error)
+_gdata_service_send_message (GDataService *self, SoupMessage *message, GCancellable *cancellable, GError **error)
 {
 	/* Based on code from evolution-data-server's libgdata:
 	 *  Ebby Wiselyn <ebbywiselyn gmail com>
@@ -858,10 +889,18 @@ _gdata_service_send_message (GDataService *self, SoupMessage *message, GError **
 	 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
 	 */
 
+	gulong cancel_signal = 0;
+
+	if (cancellable != NULL)
+		cancel_signal = g_cancellable_connect (cancellable, (GCallback) message_cancel_cb, message, NULL);
+
 	soup_message_set_flags (message, SOUP_MESSAGE_NO_REDIRECT);
 	soup_session_send_message (self->priv->session, message);
 	soup_message_set_flags (message, 0);
 
+	if (cancel_signal != 0)
+		g_cancellable_disconnect (cancellable, cancel_signal);
+
 	if (SOUP_STATUS_IS_REDIRECTION (message->status_code)) {
 		SoupURI *new_uri;
 		const gchar *new_location;
@@ -882,9 +921,19 @@ _gdata_service_send_message (GDataService *self, SoupMessage *message, GError **
 		soup_message_set_uri (message, new_uri);
 		soup_uri_free (new_uri);
 
+		/* Send the message again */
+		if (cancellable != NULL)
+			cancel_signal = g_cancellable_connect (cancellable, (GCallback) message_cancel_cb, message, NULL);
+
 		soup_session_send_message (self->priv->session, message);
+
+		if (cancel_signal != 0)
+			g_cancellable_disconnect (cancellable, cancel_signal);
 	}
 
+	if (message->status_code == SOUP_STATUS_CANCELLED)
+		g_assert (cancellable != NULL && g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE);
+
 	return message->status_code;
 }
 
@@ -918,16 +967,9 @@ query_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *c
 	GError *error = NULL;
 	QueryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
 
-	/* Check to see if it's been cancelled already */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
-		g_simple_async_result_set_from_error (result, error);
-		g_error_free (error);
-		return;
-	}
-
 	/* Execute the query and return */
 	data->feed = gdata_service_query (service, data->feed_uri, data->query, data->entry_type, cancellable,
-					  data->progress_callback, data->progress_user_data, &error);
+	                                  data->progress_callback, data->progress_user_data, &error);
 	if (data->feed == NULL && error != NULL) {
 		g_simple_async_result_set_from_error (result, error);
 		g_error_free (error);
@@ -956,8 +998,8 @@ query_thread (GSimpleAsyncResult *result, GDataService *service, GCancellable *c
  **/
 void
 gdata_service_query_async (GDataService *self, const gchar *feed_uri, GDataQuery *query, GType entry_type, GCancellable *cancellable,
-			   GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
-			   GAsyncReadyCallback callback, gpointer user_data)
+                           GDataQueryProgressCallback progress_callback, gpointer progress_user_data,
+                           GAsyncReadyCallback callback, gpointer user_data)
 {
 	GSimpleAsyncResult *result;
 	QueryAsyncData *data;
@@ -1016,44 +1058,49 @@ gdata_service_query_finish (GDataService *self, GAsyncResult *async_result, GErr
  * gdata_service_query_single_entry()) only return a single entry, and thus need special parsing code. */
 SoupMessage *
 _gdata_service_query (GDataService *self, const gchar *feed_uri, GDataQuery *query, GCancellable *cancellable,
-		      GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
+                      GDataQueryProgressCallback progress_callback, gpointer progress_user_data, GError **error)
 {
-	GDataServiceClass *klass;
 	SoupMessage *message;
 	guint status;
+	gulong cancel_signal = 0;
+	const gchar *etag = NULL;
+
+	/* Append the ETag header if possible */
+	if (query != NULL)
+		etag = gdata_query_get_etag (query);
 
+	/* Build the message */
 	if (query != NULL) {
 		gchar *query_uri = gdata_query_get_query_uri (query, feed_uri);
-		message = soup_message_new (SOUP_METHOD_GET, query_uri);
+		message = _gdata_service_build_message (self, SOUP_METHOD_GET, query_uri, etag, FALSE);
 		g_free (query_uri);
 	} else {
-		message = soup_message_new (SOUP_METHOD_GET, feed_uri);
+		message = _gdata_service_build_message (self, SOUP_METHOD_GET, feed_uri, etag, FALSE);
 	}
 
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (self);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (self, message);
-
-	/* Append the ETag header if possible */
-	if (query != NULL && gdata_query_get_etag (query) != NULL)
-		soup_message_headers_append (message->request_headers, "If-None-Match", gdata_query_get_etag (query));
+	/* TODO: Document that cancellation only applies to network activity; not to the processing done afterwards */
 
 	/* Send the message */
+	if (cancellable != NULL)
+		cancel_signal = g_cancellable_connect (cancellable, (GCallback) message_cancel_cb, message, NULL);
+
 	status = soup_session_send_message (self->priv->session, message);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	if (cancel_signal != 0)
+		g_cancellable_disconnect (cancellable, cancel_signal);
 
 	if (status == SOUP_STATUS_NOT_MODIFIED) {
 		/* Not modified; ETag has worked */
 		g_object_unref (message);
 		return NULL;
+	} else if (status == SOUP_STATUS_CANCELLED) {
+		/* Cancelled */
+		g_assert (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE);
+		g_object_unref (message);
+		return NULL;
 	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (self, GDATA_OPERATION_QUERY, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
@@ -1218,13 +1265,6 @@ query_single_entry_thread (GSimpleAsyncResult *result, GDataService *service, GC
 	GError *error = NULL;
 	QuerySingleEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
 
-	/* Check to see if it's been cancelled already */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
-		g_simple_async_result_set_from_error (result, error);
-		g_error_free (error);
-		return;
-	}
-
 	/* Execute the query and return */
 	entry = gdata_service_query_single_entry (service, data->entry_id, data->query, data->entry_type, cancellable, &error);
 	if (entry == NULL && error != NULL) {
@@ -1336,13 +1376,6 @@ insert_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
 	GError *error = NULL;
 	InsertEntryAsyncData *data = g_simple_async_result_get_op_res_gpointer (result);
 
-	/* Check to see if it's been cancelled already */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
-		g_simple_async_result_set_from_error (result, error);
-		g_error_free (error);
-		return;
-	}
-
 	/* Insert the entry and return */
 	updated_entry = gdata_service_insert_entry (service, data->upload_uri, data->entry, cancellable, &error);
 	if (updated_entry == NULL) {
@@ -1352,8 +1385,7 @@ insert_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
 	}
 
 	/* Swap the old entry with the new one */
-	g_object_unref (data->entry);
-	data->entry = updated_entry;
+	g_simple_async_result_set_op_res_gpointer (result, updated_entry, (GDestroyNotify) g_object_unref);
 }
 
 /**
@@ -1413,7 +1445,7 @@ GDataEntry *
 gdata_service_insert_entry_finish (GDataService *self, GAsyncResult *async_result, GError **error)
 {
 	GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
-	InsertEntryAsyncData *data;
+	GDataEntry *entry;
 
 	g_return_val_if_fail (GDATA_IS_SERVICE (self), NULL);
 	g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), NULL);
@@ -1424,9 +1456,9 @@ gdata_service_insert_entry_finish (GDataService *self, GAsyncResult *async_resul
 	if (g_simple_async_result_propagate_error (result, error) == TRUE)
 		return NULL;
 
-	data = g_simple_async_result_get_op_res_gpointer (result);
-	if (data->entry != NULL)
-		return g_object_ref (data->entry);
+	entry = g_simple_async_result_get_op_res_gpointer (result);
+	if (entry != NULL)
+		return g_object_ref (entry);
 
 	g_assert_not_reached ();
 }
@@ -1459,7 +1491,6 @@ gdata_service_insert_entry_finish (GDataService *self, GAsyncResult *async_resul
 GDataEntry *
 gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEntry *entry, GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataEntry *updated_entry;
 	SoupMessage *message;
 	gchar *upload_data;
@@ -1477,32 +1508,22 @@ gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEn
 		return NULL;
 	}
 
-	message = soup_message_new (SOUP_METHOD_POST, upload_uri);
-
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (self);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (self, message);
+	message = _gdata_service_build_message (self, SOUP_METHOD_POST, upload_uri, NULL, FALSE);
 
 	/* Append the data */
 	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
 	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
 
 	/* Send the message */
-	status = _gdata_service_send_message (self, message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	status = _gdata_service_send_message (self, message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return NULL;
-	}
-
-	if (status != SOUP_STATUS_CREATED) {
+	} else if (status != SOUP_STATUS_CREATED) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (self, GDATA_OPERATION_INSERTION, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
@@ -1510,10 +1531,8 @@ gdata_service_insert_entry (GDataService *self, const gchar *upload_uri, GDataEn
 		return NULL;
 	}
 
-	/* Build the updated entry */
-	g_assert (message->response_body->data != NULL);
-
 	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
+	g_assert (message->response_body->data != NULL);
 	updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), message->response_body->data, message->response_body->length,
 								  error));
 	g_object_unref (message);
@@ -1527,13 +1546,6 @@ update_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
 	GDataEntry *updated_entry;
 	GError *error = NULL;
 
-	/* Check to see if it's been cancelled already */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
-		g_simple_async_result_set_from_error (result, error);
-		g_error_free (error);
-		return;
-	}
-
 	/* Update the entry and return */
 	updated_entry = gdata_service_update_entry (service, g_simple_async_result_get_op_res_gpointer (result), cancellable, &error);
 	if (updated_entry == NULL) {
@@ -1639,7 +1651,6 @@ gdata_service_update_entry_finish (GDataService *self, GAsyncResult *async_resul
 GDataEntry *
 gdata_service_update_entry (GDataService *self, GDataEntry *entry, GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataEntry *updated_entry;
 	GDataLink *link;
 	SoupMessage *message;
@@ -1654,36 +1665,22 @@ gdata_service_update_entry (GDataService *self, GDataEntry *entry, GCancellable
 	/* Get the edit URI */
 	link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT);
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_PUT, gdata_link_get_uri (link));
-
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (self);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (self, message);
-
-	/* Append the ETag header if possible */
-	if (gdata_entry_get_etag (entry) != NULL)
-		soup_message_headers_append (message->request_headers, "If-Match", gdata_entry_get_etag (entry));
+	message = _gdata_service_build_message (self, SOUP_METHOD_PUT, gdata_link_get_uri (link), gdata_entry_get_etag (entry), TRUE);
 
 	/* Append the data */
 	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (entry));
 	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
 
 	/* Send the message */
-	status = _gdata_service_send_message (self, message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	status = _gdata_service_send_message (self, message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return NULL;
-	}
-
-	if (status != SOUP_STATUS_OK) {
+	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (self, GDATA_OPERATION_UPDATE, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
@@ -1691,10 +1688,8 @@ gdata_service_update_entry (GDataService *self, GDataEntry *entry, GCancellable
 		return NULL;
 	}
 
-	/* Build the updated entry */
-	g_assert (message->response_body->data != NULL);
-
 	/* Parse the XML; create and return a new GDataEntry of the same type as @entry */
+	g_assert (message->response_body->data != NULL);
 	updated_entry = GDATA_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (entry), message->response_body->data, message->response_body->length,
 								  error));
 	g_object_unref (message);
@@ -1708,13 +1703,6 @@ delete_entry_thread (GSimpleAsyncResult *result, GDataService *service, GCancell
 	gboolean success;
 	GError *error = NULL;
 
-	/* Check to see if it's been cancelled already */
-	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
-		g_simple_async_result_set_from_error (result, error);
-		g_error_free (error);
-		return;
-	}
-
 	/* Delete the entry and return */
 	success = gdata_service_delete_entry (service, g_simple_async_result_get_op_res_gpointer (result), cancellable, &error);
 	if (success == FALSE) {
@@ -1813,7 +1801,6 @@ gdata_service_delete_entry_finish (GDataService *self, GAsyncResult *async_resul
 gboolean
 gdata_service_delete_entry (GDataService *self, GDataEntry *entry, GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataLink *link;
 	SoupMessage *message;
 	guint status;
@@ -1826,32 +1813,18 @@ gdata_service_delete_entry (GDataService *self, GDataEntry *entry, GCancellable
 	/* Get the edit URI */
 	link = gdata_entry_look_up_link (entry, GDATA_LINK_EDIT);
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_DELETE, gdata_link_get_uri (link));
-
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (self);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (self, message);
-
-	/* Append the ETag header if possible */
-	if (gdata_entry_get_etag (entry) != NULL)
-		soup_message_headers_append (message->request_headers, "If-Match", gdata_entry_get_etag (entry));
+	message = _gdata_service_build_message (self, SOUP_METHOD_DELETE, gdata_link_get_uri (link), gdata_entry_get_etag (entry), TRUE);
 
 	/* Send the message */
-	status = _gdata_service_send_message (self, message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return FALSE;
-	}
+	status = _gdata_service_send_message (self, message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return FALSE;
-	}
-
-	if (status != SOUP_STATUS_OK) {
+	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (self, GDATA_OPERATION_DELETION, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
diff --git a/gdata/services/contacts/gdata-contacts-contact.c b/gdata/services/contacts/gdata-contacts-contact.c
index 65a6eec..8e007f2 100644
--- a/gdata/services/contacts/gdata-contacts-contact.c
+++ b/gdata/services/contacts/gdata-contacts-contact.c
@@ -2875,7 +2875,6 @@ gchar *
 gdata_contacts_contact_get_photo (GDataContactsContact *self, GDataContactsService *service, gsize *length, gchar **content_type,
 				  GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataLink *link;
 	SoupMessage *message;
 	guint status;
@@ -2885,36 +2884,29 @@ gdata_contacts_contact_get_photo (GDataContactsContact *self, GDataContactsServi
 	g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (self), NULL);
 	g_return_val_if_fail (GDATA_IS_CONTACTS_SERVICE (service), NULL);
 	g_return_val_if_fail (length != NULL, NULL);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), NULL);
+	g_return_val_if_fail (error == NULL || *error == NULL, NULL);
 
 	/* Return if there is no photo */
 	if (gdata_contacts_contact_has_photo (self) == FALSE)
 		return NULL;
 
 	/* Get the photo URI */
+	/* TODO: ETag support */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/contacts/2008/rel#photo";);
 	g_assert (link != NULL);
-	message = soup_message_new (SOUP_METHOD_GET, gdata_link_get_uri (link));
-
-	/* Make sure the headers are set */
-	klass = GDATA_SERVICE_GET_CLASS (service);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (GDATA_SERVICE (service), message);
+	message = _gdata_service_build_message (GDATA_SERVICE (service), SOUP_METHOD_GET, gdata_link_get_uri (link), NULL, FALSE);
 
 	/* Send the message */
-	status = _gdata_service_send_message (GDATA_SERVICE (service), message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	status = _gdata_service_send_message (GDATA_SERVICE (service), message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return NULL;
-	}
-
-	if (status != SOUP_STATUS_OK) {
+	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (service);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (GDATA_SERVICE (service), GDATA_OPERATION_DOWNLOAD, status, message->reason_phrase,
 		                             message->response_body->data, message->response_body->length, error);
@@ -2943,7 +2935,7 @@ gdata_contacts_contact_get_photo (GDataContactsContact *self, GDataContactsServi
  * @self: a #GDataContactsContact
  * @service: a #GDataService
  * @data: the image data, or %NULL
- * @length: the image length, in bytes, or <code class="literal">0</code>
+ * @length: the image length, in bytes
  * @cancellable: optional #GCancellable object, or %NULL
  * @error: a #GError, or %NULL
  *
@@ -2962,7 +2954,6 @@ gboolean
 gdata_contacts_contact_set_photo (GDataContactsContact *self, GDataService *service, const gchar *data, gsize length,
 				  GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataLink *link;
 	SoupMessage *message;
 	guint status;
@@ -2971,6 +2962,8 @@ gdata_contacts_contact_set_photo (GDataContactsContact *self, GDataService *serv
 	/* TODO: async version */
 	g_return_val_if_fail (GDATA_IS_CONTACTS_CONTACT (self), FALSE);
 	g_return_val_if_fail (GDATA_IS_SERVICE (service), FALSE);
+	g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+	g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
 
 	if (self->priv->photo_etag == NULL && data != NULL)
 		adding_photo = TRUE;
@@ -2980,40 +2973,23 @@ gdata_contacts_contact_set_photo (GDataContactsContact *self, GDataService *serv
 	/* Get the photo URI */
 	link = gdata_entry_look_up_link (GDATA_ENTRY (self), "http://schemas.google.com/contacts/2008/rel#photo";);
 	g_assert (link != NULL);
-	if (deleting_photo == TRUE)
-		message = soup_message_new (SOUP_METHOD_DELETE, gdata_link_get_uri (link));
-	else
-		message = soup_message_new (SOUP_METHOD_PUT, gdata_link_get_uri (link));
-
-	/* Make sure the headers are set */
-	klass = GDATA_SERVICE_GET_CLASS (service);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (service, message);
+	message = _gdata_service_build_message (GDATA_SERVICE (service), (deleting_photo == TRUE) ? SOUP_METHOD_DELETE : SOUP_METHOD_PUT,
+	                                        gdata_link_get_uri (link), self->priv->photo_etag, TRUE);
 
-	/* Append the ETag header if possible */
-	if (self->priv->photo_etag != NULL)
-		soup_message_headers_append (message->request_headers, "If-Match", self->priv->photo_etag);
-
-	if (deleting_photo == FALSE) {
-		/* Append the data */
+	/* Append the data */
+	if (deleting_photo == FALSE)
 		soup_message_set_request (message, "image/*", SOUP_MEMORY_STATIC, (gchar*) data, length);
-	}
 
 	/* Send the message */
-	status = _gdata_service_send_message (service, message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return FALSE;
-	}
+	status = _gdata_service_send_message (service, message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return FALSE;
-	}
-
-	if (status != SOUP_STATUS_OK) {
+	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (service);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (service, GDATA_OPERATION_UPLOAD, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);
diff --git a/gdata/services/documents/gdata-documents-service.c b/gdata/services/documents/gdata-documents-service.c
index dbe95d6..87770c7 100644
--- a/gdata/services/documents/gdata-documents-service.c
+++ b/gdata/services/documents/gdata-documents-service.c
@@ -506,9 +506,8 @@ GDataDocumentsEntry *
 gdata_documents_service_move_document_to_folder (GDataDocumentsService *self, GDataDocumentsEntry *document, GDataDocumentsFolder *folder,
 						 GCancellable *cancellable, GError **error)
 {
-	GDataServiceClass *klass;
 	GDataDocumentsEntry *new_document;
-	gchar *uri, *entry_xml, *upload_data;
+	gchar *uri, *upload_data;
 	const gchar *folder_id;
 	SoupMessage *message;
 	guint status;
@@ -517,6 +516,7 @@ gdata_documents_service_move_document_to_folder (GDataDocumentsService *self, GD
 	g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (document), NULL);
 	g_return_val_if_fail (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 (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
@@ -524,47 +524,27 @@ gdata_documents_service_move_document_to_folder (GDataDocumentsService *self, GD
 		return NULL;
 	}
 
+	/* TODO: ETag support */
 	folder_id = gdata_documents_entry_get_document_id (GDATA_DOCUMENTS_ENTRY (folder));
 	g_assert (folder_id != NULL);
 	uri = g_strconcat (_gdata_service_get_scheme (), "://docs.google.com/feeds/folders/private/full/folder%3A", folder_id, NULL);
-
-	message = soup_message_new (SOUP_METHOD_POST, uri);
+	message = _gdata_service_build_message (GDATA_SERVICE (self), SOUP_METHOD_POST, uri, NULL, TRUE);
 	g_free (uri);
 
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (self);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (GDATA_SERVICE (self), message);
-
-	/* Get the XML content */
-	entry_xml = gdata_parsable_get_xml (GDATA_PARSABLE (document));
-
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
-		g_object_unref (message);
-		g_free (entry_xml);
-		return NULL;
-	}
-
-	upload_data = g_strconcat ("<?xml version='1.0' encoding='UTF-8'?>", entry_xml, NULL);
-	g_free (entry_xml);
+	/* Append the data */
+	upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (document));
 	soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
 
 	/* Send the message */
-	status = _gdata_service_send_message (GDATA_SERVICE (self), message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	status = _gdata_service_send_message (GDATA_SERVICE (self), message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return NULL;
-	}
-
-	if (status != SOUP_STATUS_CREATED) {
+	} else if (status != SOUP_STATUS_CREATED) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (GDATA_SERVICE (self), GDATA_OPERATION_UPDATE, status, message->reason_phrase,
 		                             message->response_body->data, message->response_body->length, error);
@@ -572,10 +552,8 @@ gdata_documents_service_move_document_to_folder (GDataDocumentsService *self, GD
 		return NULL;
 	}
 
-	/* Build the updated entry */
+	/* Parse the XML; and update the document */
 	g_assert (message->response_body->data != NULL);
-
-	/* Parse the XML; and update the document*/
 	new_document = GDATA_DOCUMENTS_ENTRY (gdata_parsable_new_from_xml (G_OBJECT_TYPE (document), message->response_body->data,
 	                                                                   message->response_body->length, error));
 	g_object_unref (message);
@@ -604,7 +582,6 @@ gdata_documents_service_remove_document_from_folder (GDataDocumentsService *self
 						     GCancellable *cancellable, GError **error)
 {
 	const gchar *folder_id, *document_id;
-	GDataServiceClass *klass;
 	SoupMessage *message;
 	guint status;
 	gchar *uri;
@@ -613,6 +590,7 @@ gdata_documents_service_remove_document_from_folder (GDataDocumentsService *self
 	g_return_val_if_fail (GDATA_IS_DOCUMENTS_ENTRY (document), NULL);
 	g_return_val_if_fail (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 (gdata_service_is_authenticated (GDATA_SERVICE (self)) == FALSE) {
 		g_set_error_literal (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_AUTHENTICATION_REQUIRED,
@@ -642,31 +620,19 @@ gdata_documents_service_remove_document_from_folder (GDataDocumentsService *self
 		g_assert_not_reached ();
 	}
 
-	message = soup_message_new (SOUP_METHOD_DELETE, uri);
+	message = _gdata_service_build_message (GDATA_SERVICE (self), SOUP_METHOD_DELETE, uri, gdata_entry_get_etag (GDATA_ENTRY (document)), TRUE);
 	g_free (uri);
 
-	/* Make sure subclasses set their headers */
-	klass = GDATA_SERVICE_GET_CLASS (self);
-	if (klass->append_query_headers != NULL)
-		klass->append_query_headers (GDATA_SERVICE (self), message);
-
-	soup_message_headers_append (message->request_headers, "If-Match", gdata_entry_get_etag (GDATA_ENTRY (document)));
-
 	/* Send the message */
-	status = _gdata_service_send_message (GDATA_SERVICE (self), message, error);
-	if (status == SOUP_STATUS_NONE) {
-		g_object_unref (message);
-		return NULL;
-	}
+	status = _gdata_service_send_message (GDATA_SERVICE (self), message, cancellable, error);
 
-	/* Check for cancellation */
-	if (g_cancellable_set_error_if_cancelled (cancellable, error) == TRUE) {
+	if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+		/* Redirect error or cancelled */
 		g_object_unref (message);
 		return NULL;
-	}
-
-	if (status != SOUP_STATUS_OK) {
+	} else if (status != SOUP_STATUS_OK) {
 		/* Error */
+		GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (self);
 		g_assert (klass->parse_error_response != NULL);
 		klass->parse_error_response (GDATA_SERVICE (self), GDATA_OPERATION_UPDATE, status, message->reason_phrase, message->response_body->data,
 		                             message->response_body->length, error);



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