summaryrefslogtreecommitdiff
path: root/gdata/gdata-batch-operation.c
diff options
context:
space:
mode:
authorPhilip Withnall <philip@tecnocode.co.uk>2010-01-26 13:20:41 +0000
committerPhilip Withnall <philip@tecnocode.co.uk>2010-07-12 09:21:27 +0100
commit5c32aadbddb89eea4d8fc15626e179ce8a8f2d13 (patch)
treeac9abc431d6ae65554283681794cd997529c6685 /gdata/gdata-batch-operation.c
parentadf35ec6f1f46f0522d30f90140659a002c59122 (diff)
downloadlibgdata-5c32aadbddb89eea4d8fc15626e179ce8a8f2d13.tar.gz
[core] Batch operation support
Add batch operation support. Batch operations are executed by creating a new GDataBatchOperation, adding the required operations to it, then running it (sync or async). Batch operations are only supported by services which implement GDataBatchable. This includes full test cases and documentation. Closes: bgo#579169
Diffstat (limited to 'gdata/gdata-batch-operation.c')
-rw-r--r--gdata/gdata-batch-operation.c696
1 files changed, 696 insertions, 0 deletions
diff --git a/gdata/gdata-batch-operation.c b/gdata/gdata-batch-operation.c
new file mode 100644
index 00000000..e0e23dfd
--- /dev/null
+++ b/gdata/gdata-batch-operation.c
@@ -0,0 +1,696 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * GData Client
+ * Copyright (C) Philip Withnall 2010 <philip@tecnocode.co.uk>
+ *
+ * GData Client is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * GData Client is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GData Client. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * SECTION:gdata-batch-operation
+ * @short_description: GData batch operation object
+ * @stability: Unstable
+ * @include: gdata/gdata-batch-operation.h
+ *
+ * #GDataBatchOperation is a transient standalone class which represents and handles a single batch operation request to a service. To make a batch
+ * operation request: create a new #GDataBatchOperation; add the required queries, insertions, updates and deletions to the operation using
+ * gdata_batch_operation_add_query(), gdata_batch_operation_add_insertion(), gdata_batch_operation_add_update() and
+ * gdata_batch_operation_add_deletion(), respectively; run the request with gdata_batch_operation_run() or gdata_batch_operation_run_async(); and
+ * handle the results in the callback functions which are invoked by the operation as the results are received and parsed.
+ *
+ * <example>
+ * <title>Running a Synchronous Operation</title>
+ * <programlisting>
+ * guint op_id, op_id2;
+ * GDataBatchOperation *operation;
+ * GDataContactsContact *contact;
+ * GDataService *service;
+ * GDataLink *self_link;
+ *
+ * service = create_contacts_service ();
+ * contact = create_new_contact ();
+ * self_link = gdata_entry_look_up_link (other_contact, GDATA_LINK_SELF);
+ * batch_link = gdata_feed_look_up_link (contacts_feed, GDATA_LINK_BATCH);
+ *
+ * operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), gdata_link_get_uri (batch_link));
+ *
+ * /<!-- -->* Add to the operation to insert a new contact and query for another one *<!-- -->/
+ * op_id = gdata_batch_operation_add_insertion (operation, GDATA_ENTRY (contact), insertion_cb, user_data);
+ * op_id2 = gdata_batch_operation_add_query (operation, gdata_link_get_uri (self_link), GDATA_TYPE_CONTACTS_CONTACT, query_cb, user_data);
+ *
+ * g_object_unref (contact);
+ * g_object_unref (service);
+ *
+ * /<!-- -->* Run the operations in a blocking fashion. Ideally, check and free the error as appropriate after running the operation. *<!-- -->/
+ * gdata_batch_operation_run (operation, NULL, &error);
+ *
+ * g_object_unref (operation);
+ *
+ * static void
+ * insertion_cb (guint operation_id, GDataBatchOperationType operation_type, GDataEntry *entry, GError *error, gpointer user_data)
+ * {
+ * /<!-- -->* operation_id == op_id, operation_type == GDATA_BATCH_OPERATION_INSERTION *<!-- -->/
+ *
+ * /<!-- -->* Process the new inserted entry, ideally after checking for errors. Note that the entry should be reffed if it needs to stay
+ * * alive after execution of the callback finishes. *<!-- -->/
+ * process_inserted_entry (entry, user_data);
+ * }
+ *
+ * static void
+ * query_cb (guint operation_id, GDataBatchOperationType operation_type, GDataEntry *entry, GError *error, gpointer user_data)
+ * {
+ * /<!-- -->* operation_id == op_id2, operation_type == GDATA_BATCH_OPERATION_QUERY *<!-- -->/
+ *
+ * /<!-- -->* Process the results of the query, ideally after checking for errors. Note that the entry should be reffed if it needs to
+ * * stay alive after execution of the callback finishes. *<!-- -->/
+ * process_queried_entry (entry, user_data);
+ * }
+ * </programlisting>
+ * </example>
+ *
+ * Since: 0.7.0
+ **/
+
+#include <config.h>
+#include <glib.h>
+#include <string.h>
+
+#include "gdata-batch-operation.h"
+#include "gdata-batch-feed.h"
+#include "gdata-private.h"
+#include "gdata-batch-private.h"
+
+static void operation_free (BatchOperation *op);
+
+static void gdata_batch_operation_dispose (GObject *object);
+static void gdata_batch_operation_finalize (GObject *object);
+static void gdata_batch_operation_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
+static void gdata_batch_operation_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
+
+struct _GDataBatchOperationPrivate {
+ GDataService *service;
+ gchar *feed_uri;
+ GHashTable *operations;
+ guint next_id; /* next available operation ID */
+ gboolean has_run; /* TRUE if the operation has been run already (though it does not necessarily have to have finished running) */
+ gboolean is_async; /* TRUE if the operation was run with *_run_async(); FALSE if run with *_run() */
+};
+
+enum {
+ PROP_SERVICE = 1,
+ PROP_FEED_URI
+};
+
+G_DEFINE_TYPE (GDataBatchOperation, gdata_batch_operation, G_TYPE_OBJECT)
+#define GDATA_BATCH_OPERATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), GDATA_TYPE_BATCH_OPERATION, GDataBatchOperationPrivate))
+
+static void
+gdata_batch_operation_class_init (GDataBatchOperationClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (GDataBatchOperationPrivate));
+
+ gobject_class->dispose = gdata_batch_operation_dispose;
+ gobject_class->finalize = gdata_batch_operation_finalize;
+ gobject_class->get_property = gdata_batch_operation_get_property;
+ gobject_class->set_property = gdata_batch_operation_set_property;
+
+ /**
+ * GDataBatchOperation:service:
+ *
+ * The service this batch operation is attached to.
+ *
+ * Since: 0.7.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_SERVICE,
+ g_param_spec_object ("service",
+ "Service", "The service this batch operation is attached to.",
+ GDATA_TYPE_SERVICE,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+
+ /**
+ * GDataBatchOperation:feed-uri:
+ *
+ * The feed URI that this batch operation will be sent to.
+ *
+ * Since: 0.7.0
+ **/
+ g_object_class_install_property (gobject_class, PROP_FEED_URI,
+ g_param_spec_string ("feed-uri",
+ "Feed URI", "The feed URI that this batch operation will be sent to.",
+ NULL,
+ G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdata_batch_operation_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec)
+{
+ GDataBatchOperationPrivate *priv = GDATA_BATCH_OPERATION_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_SERVICE:
+ g_value_set_object (value, priv->service);
+ break;
+ case PROP_FEED_URI:
+ g_value_set_string (value, priv->feed_uri);
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_batch_operation_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec)
+{
+ GDataBatchOperationPrivate *priv = GDATA_BATCH_OPERATION_GET_PRIVATE (object);
+
+ switch (property_id) {
+ case PROP_SERVICE:
+ priv->service = g_value_dup_object (value);
+ break;
+ case PROP_FEED_URI:
+ priv->feed_uri = g_value_dup_string (value);
+ break;
+ default:
+ /* We don't have any other property... */
+ G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+gdata_batch_operation_init (GDataBatchOperation *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GDATA_TYPE_BATCH_OPERATION, GDataBatchOperationPrivate);
+ self->priv->next_id = 1; /* reserve ID 0 for error conditions */
+ self->priv->operations = g_hash_table_new_full (g_direct_hash, g_direct_equal, NULL, (GDestroyNotify) operation_free);
+}
+
+static void
+gdata_batch_operation_dispose (GObject *object)
+{
+ GDataBatchOperationPrivate *priv = GDATA_BATCH_OPERATION_GET_PRIVATE (object);
+
+ if (priv->service != NULL)
+ g_object_unref (priv->service);
+ priv->service = NULL;
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_batch_operation_parent_class)->dispose (object);
+}
+
+static void
+gdata_batch_operation_finalize (GObject *object)
+{
+ GDataBatchOperationPrivate *priv = GDATA_BATCH_OPERATION_GET_PRIVATE (object);
+
+ g_free (priv->feed_uri);
+ g_hash_table_destroy (priv->operations);
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (gdata_batch_operation_parent_class)->finalize (object);
+}
+
+/**
+ * gdata_batch_operation_get_service:
+ * @self: a #GDataBatchOperation
+ *
+ * Gets the #GDataBatchOperation:service property.
+ *
+ * Return value: the batch operation's attached service
+ *
+ * Since: 0.7.0
+ **/
+GDataService *
+gdata_batch_operation_get_service (GDataBatchOperation *self)
+{
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), NULL);
+ return self->priv->service;
+}
+
+/**
+ * gdata_batch_operation_get_feed_uri:
+ * @self: a #GDataBatchOperation
+ *
+ * Gets the #GDataBatchOperation:feed-uri property.
+ *
+ * Return value: the batch operation's feed URI
+ *
+ * Since: 0.7.0
+ **/
+const gchar *
+gdata_batch_operation_get_feed_uri (GDataBatchOperation *self)
+{
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), NULL);
+ return self->priv->feed_uri;
+}
+
+/* Add an operation to the list of operations to be executed when the #GDataBatchOperation is run, and return its operation ID */
+static guint
+add_operation (GDataBatchOperation *self, GDataBatchOperationType type, GDataEntry *entry, GDataBatchOperationCallback callback, gpointer user_data)
+{
+ BatchOperation *op;
+
+ /* Create the operation */
+ op = g_slice_new0 (BatchOperation);
+ op->id = (self->priv->next_id++);
+ op->type = type;
+ op->callback = callback;
+ op->user_data = user_data;
+ op->entry = g_object_ref (entry);
+
+ /* Add the operation to the table */
+ g_hash_table_insert (self->priv->operations, GUINT_TO_POINTER (op->id), op);
+
+ return op->id;
+}
+
+/**
+ * _gdata_batch_operation_get_operation:
+ * @self: a #GDataBatchOperation
+ * @id: the operation ID
+ *
+ * Return the #BatchOperation for the given operation ID.
+ *
+ * Return value: the relevant #BatchOperation, or %NULL
+ *
+ * Since: 0.7.0
+ **/
+BatchOperation *
+_gdata_batch_operation_get_operation (GDataBatchOperation *self, guint id)
+{
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), NULL);
+ g_return_val_if_fail (id > 0, NULL);
+
+ return g_hash_table_lookup (self->priv->operations, GUINT_TO_POINTER (id));
+}
+
+/* Run a user-supplied callback for a #BatchOperation whose return value we've just processed. This is designed to be used in an idle handler, so
+ * that the callback is run in the main thread. It should not be called if the user-supplied callback is %NULL. */
+static gboolean
+run_callback_cb (BatchOperation *op)
+{
+ /* We do allow op->callback to be unset, but in that case run_callback_cb() shouldn't be being called */
+ g_assert (op->callback != NULL);
+ op->callback (op->id, op->type, op->entry, op->error, op->user_data);
+
+ return FALSE;
+}
+
+/**
+ * _gdata_batch_operation_run_callback:
+ * @self: a #GDataBatchOperation
+ * @op: the #BatchOperation which has been finished
+ * @entry: the entry representing the operation's result, or %NULL
+ * @error: the error from the operation, or %NULL
+ *
+ * Run the callback for @op to notify the user code that the operation's result has been received and processed. Either @entry or @error should be
+ * set (and the other should be %NULL), signifying a successful operation or a failed operation, respectively.
+ *
+ * The function will call @op's user-supplied callback, if available, in either the current or the main thread, depending on whether the
+ * #GDataBatchOperation was run with gdata_batch_operation_run() or gdata_batch_operation_run_async().
+ *
+ * Since: 0.7.0
+ **/
+void
+_gdata_batch_operation_run_callback (GDataBatchOperation *self, BatchOperation *op, GDataEntry *entry, GError *error)
+{
+ g_return_if_fail (GDATA_IS_BATCH_OPERATION (self));
+ g_return_if_fail (op != NULL);
+ g_return_if_fail (entry == NULL || GDATA_IS_ENTRY (entry));
+ g_return_if_fail (entry == NULL || error == NULL);
+
+ /* We can free the request data, and replace it with the response data */
+ g_free (op->query_id);
+ op->query_id = NULL;
+ if (op->entry != NULL)
+ g_object_unref (op->entry);
+ if (entry != NULL)
+ g_object_ref (entry);
+ op->entry = entry;
+ op->error = error;
+
+ /* Don't bother scheduling run_callback_cb() if there is no callback to run */
+ if (op->callback == NULL)
+ return;
+
+ /* Only dispatch it in the main thread if the request was run with *_run_async(). This allows applications to run batch operations entirely in
+ * application-owned threads if desired. */
+ if (self->priv->is_async == TRUE)
+ g_idle_add ((GSourceFunc) run_callback_cb, op);
+ else
+ run_callback_cb (op);
+}
+
+/* Free a #BatchOperation */
+static void
+operation_free (BatchOperation *op)
+{
+ g_free (op->query_id);
+ if (op->entry != NULL)
+ g_object_unref (op->entry);
+ if (op->error != NULL)
+ g_error_free (op->error);
+
+ g_slice_free (BatchOperation, op);
+}
+
+/**
+ * gdata_batch_operation_add_query:
+ * @self: a #GDataBatchOperation
+ * @id: the ID of the entry being queried for
+ * @entry_type: the type of the entry which will be returned
+ * @callback: a #GDataBatchOperationCallback to call when the query is finished, or %NULL
+ * @user_data: data to pass to the @callback function
+ *
+ * Add a query to the #GDataBatchOperation, to be executed when the operation is run. The query will return a #GDataEntry (of subclass type
+ * @entry_type) representing the given entry @id. The ID is of the same format as that returned by gdata_entry_get_id().
+ *
+ * Note that a single batch operation should not operate on a given #GDataEntry more than once, as there's no guarantee about the order in which the
+ * batch operation's operations will be performed.
+ *
+ * @callback will be called when the #GDataBatchOperation is run with gdata_batch_operation_run() (in which case it will be called in the thread which
+ * ran the batch operation), or with gdata_batch_operation_run_async() (in which case it will be called in an idle handler in the main thread). The
+ * @operation_id passed to the callback will match the return value of gdata_batch_operation_add_query(), and the @operation_type will be
+ * %GDATA_BATCH_OPERATION_QUERY. If the query was successful, the resulting entry will be passed to the callback function as @entry, and @error will
+ * be %NULL. If, however, the query was unsuccessful, @entry will be %NULL and @error will contain a #GError detailing what went wrong.
+ *
+ * Return value: operation ID for the added query, or <code class="literal">0</code>
+ *
+ * Since: 0.7.0
+ **/
+guint
+gdata_batch_operation_add_query (GDataBatchOperation *self, const gchar *id, GType entry_type,
+ GDataBatchOperationCallback callback, gpointer user_data)
+{
+ BatchOperation *op;
+
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), 0);
+ g_return_val_if_fail (id != NULL, 0);
+ g_return_val_if_fail (g_type_is_a (entry_type, GDATA_TYPE_ENTRY), 0);
+ g_return_val_if_fail (self->priv->has_run == FALSE, 0);
+
+ /* Create the operation manually, since it would be messy to special-case add_operation() to do this */
+ op = g_slice_new0 (BatchOperation);
+ op->id = (self->priv->next_id++);
+ op->type = GDATA_BATCH_OPERATION_QUERY;
+ op->callback = callback;
+ op->user_data = user_data;
+ op->query_id = g_strdup (id);
+ op->entry_type = entry_type;
+
+ /* Add the operation to the table */
+ g_hash_table_insert (self->priv->operations, GUINT_TO_POINTER (op->id), op);
+
+ return op->id;
+}
+
+/**
+ * gdata_batch_operation_add_insertion:
+ * @self: a #GDataBatchOperation
+ * @entry: the #GDataEntry to insert
+ * @callback: a #GDataBatchOperationCallback to call when the insertion is finished, or %NULL
+ * @user_data: data to pass to the @callback function
+ *
+ * Add an entry to the #GDataBatchOperation, to be inserted on the server when the operation is run. The insertion will return the inserted version
+ * of @entry. @entry is reffed by the function, so may be freed after it returns.
+ *
+ * @callback will be called as specified in the documentation for gdata_batch_operation_add_query(), with an @operation_type of
+ * %GDATA_BATCH_OPERATION_INSERTION.
+ *
+ * Return value: operation ID for the added insertion, or <code class="literal">0</code>
+ *
+ * Since: 0.7.0
+ **/
+guint
+gdata_batch_operation_add_insertion (GDataBatchOperation *self, GDataEntry *entry, GDataBatchOperationCallback callback, gpointer user_data)
+{
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), 0);
+ g_return_val_if_fail (GDATA_IS_ENTRY (entry), 0);
+ g_return_val_if_fail (self->priv->has_run == FALSE, 0);
+
+ return add_operation (self, GDATA_BATCH_OPERATION_INSERTION, entry, callback, user_data);
+}
+
+/**
+ * gdata_batch_operation_add_update:
+ * @self: a #GDataBatchOperation
+ * @entry: the #GDataEntry to update
+ * @callback: a #GDataBatchOperationCallback to call when the update is finished, or %NULL
+ * @user_data: data to pass to the @callback function
+ *
+ * Add an entry to the #GDataBatchOperation, to be updated on the server when the operation is run. The update will return the updated version of
+ * @entry. @entry is reffed by the function, so may be freed after it returns.
+ *
+ * Note that a single batch operation should not operate on a given #GDataEntry more than once, as there's no guarantee about the order in which the
+ * batch operation's operations will be performed.
+ *
+ * @callback will be called as specified in the documentation for gdata_batch_operation_add_query(), with an @operation_type of
+ * %GDATA_BATCH_OPERATION_UPDATE.
+ *
+ * Return value: operation ID for the added update, or <code class="literal">0</code>
+ *
+ * Since: 0.7.0
+ **/
+guint
+gdata_batch_operation_add_update (GDataBatchOperation *self, GDataEntry *entry, GDataBatchOperationCallback callback, gpointer user_data)
+{
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), 0);
+ g_return_val_if_fail (GDATA_IS_ENTRY (entry), 0);
+ g_return_val_if_fail (self->priv->has_run == FALSE, 0);
+
+ return add_operation (self, GDATA_BATCH_OPERATION_UPDATE, entry, callback, user_data);
+}
+
+/**
+ * gdata_batch_operation_add_deletion:
+ * @self: a #GDataBatchOperation
+ * @entry: the #GDataEntry to delete
+ * @callback: a #GDataBatchOperationCallback to call when the deletion is finished, or %NULL
+ * @user_data: data to pass to the @callback function
+ *
+ * Add an entry to the #GDataBatchOperation, to be deleted on the server when the operation is run. @entry is reffed by the function, so may be freed
+ * after it returns.
+ *
+ * Note that a single batch operation should not operate on a given #GDataEntry more than once, as there's no guarantee about the order in which the
+ * batch operation's operations will be performed.
+ *
+ * @callback will be called as specified in the documentation for gdata_batch_operation_add_query(), with an @operation_type of
+ * %GDATA_BATCH_OPERATION_DELETION.
+ *
+ * Return value: operation ID for the added deletion, or <code class="literal">0</code>
+ *
+ * Since: 0.7.0
+ **/
+guint
+gdata_batch_operation_add_deletion (GDataBatchOperation *self, GDataEntry *entry, GDataBatchOperationCallback callback, gpointer user_data)
+{
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), 0);
+ g_return_val_if_fail (GDATA_IS_ENTRY (entry), 0);
+ g_return_val_if_fail (self->priv->has_run == FALSE, 0);
+
+ return add_operation (self, GDATA_BATCH_OPERATION_DELETION, entry, callback, user_data);
+}
+
+/* Called for each BatchOperation in GDataBatchOperation->operations to add it to a request feed */
+static void
+run_cb (gpointer key, BatchOperation *op, GDataFeed *feed)
+{
+ if (op->type == GDATA_BATCH_OPERATION_QUERY) {
+ /* Queries are weird; build a new throwaway entry, and add it to the feed */
+ GDataEntry *entry;
+ GTimeVal updated;
+
+ g_get_current_time (&updated);
+
+ entry = gdata_entry_new (op->query_id);
+ gdata_entry_set_title (entry, "Batch operation query");
+ _gdata_entry_set_updated (entry, &updated);
+
+ _gdata_entry_set_batch_data (entry, op->id, op->type);
+ _gdata_feed_add_entry (feed, entry);
+
+ g_object_unref (entry);
+ } else {
+ /* Everything else just dumps the entry's XML in the request */
+ _gdata_entry_set_batch_data (op->entry, op->id, op->type);
+ _gdata_feed_add_entry (feed, op->entry);
+ }
+}
+
+/**
+ * gdata_batch_operation_run:
+ * @self: a #GDataBatchOperation
+ * @cancellable: a #GCancellable, or %NULL
+ * @error: a #GError, or %NULL
+ *
+ * Run the #GDataBatchOperation synchronously. This will send all the operations in the batch operation to the server, and call their respective
+ * callbacks synchronously (i.e. before gdata_batch_operation_run() returns, and in the same thread that called gdata_batch_operation_run()) as the
+ * server returns results for each operation.
+ *
+ * The return value of the function indicates whether the overall batch operation was successful, and doesn't indicate the status of any of the
+ * operations it comprises. gdata_batch_operation_run() could return %TRUE even if all of its operations failed.
+ *
+ * @cancellable can be used to cancel the entire batch operation any time before or during the network activity. If @cancellable is cancelled
+ * after network activity has finished, gdata_batch_operation_run() will continue and finish as normal.
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ *
+ * Since: 0.7.0
+ **/
+gboolean
+gdata_batch_operation_run (GDataBatchOperation *self, GCancellable *cancellable, GError **error)
+{
+ GDataBatchOperationPrivate *priv = self->priv;
+ SoupMessage *message;
+ GDataFeed *feed;
+ GTimeVal updated;
+ gchar *upload_data;
+ guint status;
+
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), FALSE);
+ g_return_val_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+ g_return_val_if_fail (priv->has_run == FALSE, FALSE);
+
+ message = _gdata_service_build_message (priv->service, SOUP_METHOD_POST, priv->feed_uri, NULL, TRUE);
+
+ /* Build the request */
+ g_get_current_time (&updated);
+ feed = _gdata_feed_new ("Batch operation feed", "batch1", &updated);
+ g_hash_table_foreach (priv->operations, (GHFunc) run_cb, feed);
+
+ upload_data = gdata_parsable_get_xml (GDATA_PARSABLE (feed));
+ soup_message_set_request (message, "application/atom+xml", SOUP_MEMORY_TAKE, upload_data, strlen (upload_data));
+
+ g_object_unref (feed);
+
+ /* Ensure that this GDataBatchOperation can't be run again */
+ priv->has_run = TRUE;
+
+ /* Send the message */
+ status = _gdata_service_send_message (priv->service, message, cancellable, error);
+
+ if (status == SOUP_STATUS_NONE || status == SOUP_STATUS_CANCELLED) {
+ /* Redirect error or cancelled */
+ g_object_unref (message);
+ return FALSE;
+ } else if (status != SOUP_STATUS_OK) {
+ /* Error */
+ GDataServiceClass *klass = GDATA_SERVICE_GET_CLASS (priv->service);
+ g_assert (klass->parse_error_response != NULL);
+ klass->parse_error_response (priv->service, GDATA_OPERATION_BATCH, status, message->reason_phrase, message->response_body->data,
+ message->response_body->length, error);
+ g_object_unref (message);
+ return FALSE;
+ }
+
+ /* Parse the XML; GDataBatchFeed will fire off the relevant callbacks */
+ g_assert (message->response_body->data != NULL);
+ feed = GDATA_FEED (_gdata_parsable_new_from_xml (GDATA_TYPE_BATCH_FEED, message->response_body->data, message->response_body->length,
+ self, error));
+ g_object_unref (message);
+
+ if (feed == NULL)
+ return FALSE;
+ g_object_unref (feed);
+
+ return TRUE;
+}
+
+static void
+run_thread (GSimpleAsyncResult *result, GDataBatchOperation *operation, GCancellable *cancellable)
+{
+ gboolean success;
+ GError *error = NULL;
+
+ /* Run the batch operation and return */
+ success = gdata_batch_operation_run (operation, cancellable, &error);
+ g_simple_async_result_set_op_res_gboolean (result, success);
+
+ /* Propagate any errors */
+ if (success == FALSE) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ }
+}
+
+/**
+ * gdata_batch_operation_run_async:
+ * @self: a #GDataBatchOperation
+ * @cancellable: a #GCancellable, or %NULL
+ * @callback: a #GAsyncReadyCallback to call when the batch operation is finished, or %NULL
+ * @user_data: data to pass to the @callback function
+ *
+ * Run the #GDataBatchOperation asynchronously. This will send all the operations in the batch operation to the server, and call their respective
+ * callbacks asynchronously (i.e. in idle functions in the main thread, usually after gdata_batch_operation_run_async() has returned) as the
+ * server returns results for each operation. @self is reffed when this function is called, so can safely be unreffed after this function returns.
+ *
+ * For more details, see gdata_batch_operation_run(), which is the synchronous version of this function.
+ *
+ * When the entire batch operation is finished, @callback will be called. You can then call gdata_batch_operation_run_finish() to get the results of
+ * the batch operation.
+ *
+ * Since: 0.7.0
+ **/
+void
+gdata_batch_operation_run_async (GDataBatchOperation *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ g_return_if_fail (GDATA_IS_BATCH_OPERATION (self));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+ g_return_if_fail (self->priv->has_run == FALSE);
+
+ /* Mark the operation as async for the purposes of deciding where to call the callbacks */
+ self->priv->is_async = TRUE;
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, gdata_batch_operation_run_async);
+ g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) run_thread, G_PRIORITY_DEFAULT, cancellable);
+ g_object_unref (result);
+}
+
+/**
+ * gdata_batch_operation_run_finish:
+ * @self: a #GDataBatchOperation
+ * @async_result: a #GAsyncResult
+ * @error: a #GError, or %NULL
+ *
+ * Finishes an asynchronous batch operation run with gdata_batch_operation_run_async().
+ *
+ * Return values are as for gdata_batch_operation_run().
+ *
+ * Return value: %TRUE on success, %FALSE otherwise
+ *
+ * Since: 0.7.0
+ **/
+gboolean
+gdata_batch_operation_run_finish (GDataBatchOperation *self, GAsyncResult *async_result, GError **error)
+{
+ GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
+
+ g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (result) == gdata_batch_operation_run_async);
+
+ if (g_simple_async_result_propagate_error (result, error) == TRUE)
+ return FALSE;
+
+ return g_simple_async_result_get_op_res_gboolean (result);
+}