/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */ /* * GData Client * Copyright (C) Philip Withnall 2010 * * 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 . */ /** * SECTION:gdata-batch-operation * @short_description: GData batch operation object * @stability: Stable * @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. * * If authorization is required for any of the requests in the batch operation, the #GDataService set in #GDataBatchOperation:service must have * a #GDataAuthorizer set as its #GDataService:authorizer property, and that authorizer must be authorized for the #GDataAuthorizationDomain set * in #GDataBatchOperation:authorization-domain. It's not possible for requests in a single batch operation to be authorized under multiple domains; * in that case, the requests must be split up across several batch operations using different authorization domains. * * If all of the requests in the batch operation don't require authorization (i.e. they all operate on public data; see the documentation for the * #GDataService subclass in question's operations for details of which require authorization), #GDataBatchOperation:authorization-domain can be set * to %NULL to save the overhead of sending authorization data to the online service. * * * Running a Synchronous Operation * * guint op_id, op_id2; * GDataBatchOperation *operation; * GDataContactsContact *contact; * GDataService *service; * GDataAuthorizationDomain *domain; * * service = create_contacts_service (); * domain = get_authorization_domain_from_service (service); * contact = create_new_contact (); * batch_link = gdata_feed_look_up_link (contacts_feed, GDATA_LINK_BATCH); * * operation = gdata_batchable_create_operation (GDATA_BATCHABLE (service), domain, 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_entry_get_id (other_contact), GDATA_TYPE_CONTACTS_CONTACT, query_cb, user_data); * * g_object_unref (contact); * g_object_unref (domain); * 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); * } * * * * Since: 0.7.0 */ #include #include #include #include #include "gdata-batch-operation.h" #include "gdata-batch-feed.h" #include "gdata-batchable.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; GDataAuthorizationDomain *authorization_domain; 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, PROP_AUTHORIZATION_DOMAIN, }; 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:authorization-domain: * * The authorization domain for the batch operation, against which the #GDataService:authorizer for the #GDataBatchOperation:service should be * authorized. This may be %NULL if authorization is not needed for any of the requests in the batch operation. * * All requests in the batch operation must be authorizable under this single authorization domain. If requests need different authorization * domains, they must be performed in different batch operations. * * Since: 0.9.0 */ g_object_class_install_property (gobject_class, PROP_AUTHORIZATION_DOMAIN, g_param_spec_object ("authorization-domain", "Authorization domain", "The authorization domain for the batch operation.", GDATA_TYPE_AUTHORIZATION_DOMAIN, 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_AUTHORIZATION_DOMAIN: g_value_set_object (value, priv->authorization_domain); 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; /* Construct only */ case PROP_AUTHORIZATION_DOMAIN: priv->authorization_domain = 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->authorization_domain != NULL) g_object_unref (priv->authorization_domain); priv->authorization_domain = NULL; 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: (transfer none): 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_authorization_domain: * @self: a #GDataBatchOperation * * Gets the #GDataBatchOperation:authorization-domain property. * * Return value: (transfer none) (allow-none): the #GDataAuthorizationDomain used to authorize the batch operation, or %NULL * * Since: 0.9.0 */ GDataAuthorizationDomain * gdata_batch_operation_get_authorization_domain (GDataBatchOperation *self) { g_return_val_if_fail (GDATA_IS_BATCH_OPERATION (self), NULL); return self->priv->authorization_domain; } /** * 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 can be called if the user-supplied callback is %NULL (e.g. in the case that the callback's been * called before). */ static gboolean run_callback_cb (BatchOperation *op) { if (op->callback != NULL) op->callback (op->id, op->type, op->entry, op->error, op->user_data); /* Unset the callback so that it can't be called again */ op->callback = NULL; return FALSE; } /** * _gdata_batch_operation_run_callback: * @self: a #GDataBatchOperation * @op: the #BatchOperation which has been finished * @entry: (allow-none): 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) { /* Send the callback; use G_PRIORITY_DEFAULT rather than G_PRIORITY_DEFAULT_IDLE * to contend with the priorities used by the callback functions in GAsyncResult */ g_idle_add_full (G_PRIORITY_DEFAULT, (GSourceFunc) run_callback_cb, op, NULL); } 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: (scope async): a #GDataBatchOperationCallback to call when the query is finished, or %NULL * @user_data: (closure): 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 0 * * 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: (scope async): a #GDataBatchOperationCallback to call when the insertion is finished, or %NULL * @user_data: (closure): 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 0 * * 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: (scope async): a #GDataBatchOperationCallback to call when the update is finished, or %NULL * @user_data: (closure): 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 0 * * 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: (scope async): a #GDataBatchOperationCallback to call when the deletion is finished, or %NULL * @user_data: (closure): 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 0 * * 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); } /** * gdata_batch_operation_run: * @self: a #GDataBatchOperation * @cancellable: (allow-none): 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 callbacks for all of the operations in the batch operation are always guaranteed to be called, even if the batch operation as a whole fails. * Each callback will be called exactly once for each time gdata_batch_operation_run() is called. * * 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; GHashTableIter iter; gpointer op_id; BatchOperation *op; GError *child_error = NULL; 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); /* Check for early cancellation. */ if (g_cancellable_set_error_if_cancelled (cancellable, error)) { return FALSE; } /* Check whether the service actually supports these kinds of * operations. */ g_hash_table_iter_init (&iter, priv->operations); while (g_hash_table_iter_next (&iter, &op_id, (gpointer*) &op) == TRUE) { GDataBatchable *batchable = GDATA_BATCHABLE (priv->service); GDataBatchableIface *batchable_iface; batchable_iface = GDATA_BATCHABLE_GET_IFACE (batchable); if (batchable_iface->is_supported != NULL && !batchable_iface->is_supported (op->type)) { g_set_error (error, GDATA_SERVICE_ERROR, GDATA_SERVICE_ERROR_WITH_BATCH_OPERATION, _("Batch operations are unsupported by " "this service.")); return FALSE; } } message = _gdata_service_build_message (priv->service, priv->authorization_domain, SOUP_METHOD_POST, priv->feed_uri, NULL, TRUE); /* Build the request */ g_get_current_time (&updated); feed = _gdata_feed_new (GDATA_TYPE_FEED, "Batch operation feed", "batch1", updated.tv_sec); g_hash_table_iter_init (&iter, priv->operations); while (g_hash_table_iter_next (&iter, &op_id, (gpointer*) &op) == TRUE) { if (op->type == GDATA_BATCH_OPERATION_QUERY) { /* Queries are weird; build a new throwaway entry, and add it to the feed */ GDataEntry *entry; GDataEntryClass *klass; gchar *entry_uri; klass = g_type_class_ref (op->entry_type); g_assert (klass->get_entry_uri != NULL); entry_uri = klass->get_entry_uri (op->query_id); entry = gdata_entry_new (entry_uri); g_free (entry_uri); gdata_entry_set_title (entry, "Batch operation query"); _gdata_entry_set_updated (entry, updated.tv_sec); _gdata_entry_set_batch_data (entry, op->id, op->type); _gdata_feed_add_entry (feed, entry); g_type_class_unref (klass); 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); } } 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, &child_error); if (status != SOUP_STATUS_OK) { /* Iff status is SOUP_STATUS_NONE or SOUP_STATUS_CANCELLED, child_error has already been set */ if (status != SOUP_STATUS_NONE && status != SOUP_STATUS_CANCELLED) { /* 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, &child_error); } g_object_unref (message); goto error; } /* 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, &child_error)); g_object_unref (message); if (feed == NULL) goto error; g_object_unref (feed); return TRUE; error: /* Call the callbacks for each of our operations to notify them of the error */ g_hash_table_iter_init (&iter, priv->operations); while (g_hash_table_iter_next (&iter, &op_id, (gpointer*) &op) == TRUE) _gdata_batch_operation_run_callback (self, op, NULL, g_error_copy (child_error)); g_propagate_error (error, child_error); return FALSE; } static void run_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable) { GDataBatchOperation *operation = GDATA_BATCH_OPERATION (source_object); g_autoptr(GError) error = NULL; /* Run the batch operation and return */ if (!gdata_batch_operation_run (operation, cancellable, &error)) g_task_return_error (task, g_steal_pointer (&error)); else g_task_return_boolean (task, TRUE); } /** * gdata_batch_operation_run_async: * @self: a #GDataBatchOperation * @cancellable: (allow-none): a #GCancellable, or %NULL * @callback: a #GAsyncReadyCallback to call when the batch operation is finished, or %NULL * @user_data: (closure): 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) { g_autoptr(GTask) task = NULL; 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; task = g_task_new (self, cancellable, callback, user_data); g_task_set_source_tag (task, gdata_batch_operation_run_async); g_task_run_in_thread (task, run_thread); } /** * 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) { GDataBatchOperationPrivate *priv = self->priv; g_autoptr(GError) child_error = NULL; 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_return_val_if_fail (g_task_is_valid (async_result, self), FALSE); g_return_val_if_fail (g_async_result_is_tagged (async_result, gdata_batch_operation_run_async), FALSE); if (!g_task_propagate_boolean (G_TASK (async_result), &child_error)) { if (priv->has_run == FALSE) { GHashTableIter iter; gpointer op_id; BatchOperation *op; /* Temporarily mark the operation as synchronous so that the callbacks get dispatched in this thread */ priv->is_async = FALSE; /* If has_run hasn't been set, the call to gdata_batch_operation_run() was never made in the thread, and so none of the * operations' callbacks have been called. Call the callbacks for each of our operations to notify them of the error. * If has_run has been set, gdata_batch_operation_run() has already done this for us. */ g_hash_table_iter_init (&iter, priv->operations); while (g_hash_table_iter_next (&iter, &op_id, (gpointer*) &op) == TRUE) _gdata_batch_operation_run_callback (self, op, NULL, g_error_copy (child_error)); priv->is_async = TRUE; } g_propagate_error (error, g_steal_pointer (&child_error)); return FALSE; } return TRUE; }