1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825
|
/* -*- 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: 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.
*
* <example>
* <title>Running a Synchronous Operation</title>
* <programlisting>
* 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);
* }
* </programlisting>
* </example>
*
* Since: 0.7.0
*/
#include <config.h>
#include <glib.h>
#include <glib/gi18n-lib.h>
#include <string.h>
#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 <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: (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 <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: (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 <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: (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 <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);
}
/**
* 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;
gint64 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 */
updated = g_get_real_time () / G_USEC_PER_SEC;
feed = _gdata_feed_new (GDATA_TYPE_FEED, "Batch operation feed",
"batch1", updated);
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);
_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;
}
|