[evolution-data-server] e-book-client: add e_book_client_add_contacts*() methods



commit e91992d774039224aceef2dd5c7d2d0f2baf366b
Author: Christophe Dumez <christophe dumez intel com>
Date:   Tue Sep 27 13:35:36 2011 +0300

    e-book-client: add e_book_client_add_contacts*() methods
    
    Enables optimizations for batch additions of contacts such as
    less DBus round trips and use of database transactions in file
    backend.

 addressbook/backends/file/e-book-backend-file.c    |  185 +++++++++++-----
 .../backends/google/e-book-backend-google.c        |   42 +++-
 addressbook/backends/ldap/e-book-backend-ldap.c    |   61 +++--
 addressbook/backends/vcf/e-book-backend-vcf.c      |   35 +++-
 .../backends/webdav/e-book-backend-webdav.c        |   51 +++--
 addressbook/libebook/e-book-client.c               |  233 ++++++++++++++++----
 addressbook/libebook/e-book-client.h               |    4 +
 addressbook/libebook/e-book.c                      |   40 +++--
 addressbook/libedata-book/e-book-backend-sync.c    |   50 ++--
 addressbook/libedata-book/e-book-backend-sync.h    |    4 +-
 addressbook/libedata-book/e-book-backend.c         |   22 +-
 addressbook/libedata-book/e-book-backend.h         |    4 +-
 addressbook/libedata-book/e-data-book.c            |   58 +++--
 addressbook/libedata-book/e-data-book.h            |    2 +-
 addressbook/libegdbus/e-gdbus-book.c               |   48 ++--
 addressbook/libegdbus/e-gdbus-book.h               |   14 +-
 16 files changed, 587 insertions(+), 266 deletions(-)
---
diff --git a/addressbook/backends/file/e-book-backend-file.c b/addressbook/backends/file/e-book-backend-file.c
index 38652b8..da34e5a 100644
--- a/addressbook/backends/file/e-book-backend-file.c
+++ b/addressbook/backends/file/e-book-backend-file.c
@@ -296,79 +296,133 @@ set_revision (EContact *contact)
 
 }
 
+/**
+ * This method will return TRUE if all the contacts were properly created.
+ * If at least one contact fails, the method will return FALSE, all
+ * changes will be reverted (the @contacts list will stay empty) and
+ * @perror will be set.
+ */
 static gboolean
 do_create (EBookBackendFile *bf,
-          const gchar *vcard_req,
-          EContact **contact,
+          const GSList *vcards_req,
+          GSList **contacts,
           GError **perror)
 {
-	DB             *db = bf->priv->file_db;
-	DBT            id_dbt, vcard_dbt;
-	gint            db_error;
-	gchar           *id;
-	gchar           *vcard;
-	const gchar *rev;
+	DB *db = bf->priv->file_db;
+	DB_ENV *env = bf->priv->env;
+	DB_TXN *txn = NULL;
+	GSList *slist = NULL;
+	const GSList *l;
+	gint db_error = 0;
 
 	g_assert (bf);
-	g_assert (vcard_req);
-	g_assert (contact);
+	g_assert (vcards_req);
 
 	if (!db) {
 		g_propagate_error (perror, EDB_NOT_OPENED_ERROR);
 		return FALSE;
 	}
 
-	id = e_book_backend_file_create_unique_id ();
+	/* Begin transaction */
+	db_error = env->txn_begin(env, NULL, &txn, 0);
+	if (db_error != 0) {
+		g_warning (G_STRLOC ": env->txn_begin failed with %s", db_strerror (db_error));
+		db_error_to_gerror (db_error, perror);
+		return FALSE;
+	}
+
+	for (l = vcards_req; l != NULL; l = l->next) {
+		DBT            id_dbt, vcard_dbt;
+		gchar           *id;
+		gchar           *vcard;
+		const gchar *rev;
+		const gchar *vcard_req;
+		EContact *contact;
 
-	string_to_dbt (id, &id_dbt);
+		vcard_req = (const gchar*) l->data;
 
-	*contact = e_contact_new_from_vcard_with_uid (vcard_req, id);
-	rev = e_contact_get_const (*contact,  E_CONTACT_REV);
-	if (!(rev && *rev))
-		set_revision (*contact);
+		id = e_book_backend_file_create_unique_id ();
 
-	vcard = e_vcard_to_string (E_VCARD (*contact), EVC_FORMAT_VCARD_30);
+		string_to_dbt (id, &id_dbt);
 
-	string_to_dbt (vcard, &vcard_dbt);
+		contact = e_contact_new_from_vcard_with_uid (vcard_req, id);
 
-	db_error = db->put (db, NULL, &id_dbt, &vcard_dbt, 0);
+		rev = e_contact_get_const (contact, E_CONTACT_REV);
+		if (!(rev && *rev))
+			set_revision (contact);
 
-	g_free (vcard);
+		vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
 
-	if (0 == db_error) {
-		db_error = db->sync (db, 0);
-		if (db_error != 0) {
-			g_warning ("db->sync failed with %s", db_strerror (db_error));
+		string_to_dbt (vcard, &vcard_dbt);
+
+		db_error = db->put (db, txn, &id_dbt, &vcard_dbt, 0);
+
+		g_free (vcard);
+		g_free (id);
+
+		if (db_error == 0) {
+			/* Contact was added successfully, add it to the return list */
+			if (contacts != NULL)
+				slist = g_slist_prepend (slist, contact);
+		} else {
+			/* Contact could not be added */
+			g_warning (G_STRLOC ": db->put failed with %s", db_strerror (db_error));
+			g_object_unref (contact);
+			db_error_to_gerror (db_error, perror);
+
+			/* Abort as soon as an error occurs */
+			break;
+		}
+	}
+
+	if (db_error == 0) {
+		/* Commit transaction */
+		db_error = txn->commit (txn, 0);
+		if (db_error == 0) {
+			/* Flush cache information to disk */
+			if (db->sync (db, 0) != 0) {
+				g_warning ("db->sync failed with %s", db_strerror (db_error));
+			}
+		} else {
+			g_warning (G_STRLOC ": txn->commit failed with %s", db_strerror (db_error));
+			db_error_to_gerror (db_error, perror);
 		}
 	} else {
-		g_warning (G_STRLOC ": db->put failed with %s", db_strerror (db_error));
-		g_object_unref (*contact);
-		*contact = NULL;
+		/* Rollback transaction */
+		txn->abort (txn);
 	}
 
-	g_free (id);
-	db_error_to_gerror (db_error, perror);
+	if (db_error == 0) {
+		if (contacts != NULL)
+			*contacts = g_slist_reverse (slist);
+
+		return TRUE;
+	} else {
+		if (contacts != NULL)
+			*contacts = NULL;
 
-	return db_error == 0;
+		e_util_free_object_slist (slist);
+		return FALSE;
+	}
 }
 
 static void
-e_book_backend_file_create_contact (EBookBackendSync *backend,
-                                    EDataBook *book,
-                                    GCancellable *cancellable,
-                                    const gchar *vcard,
-                                    EContact **contact,
-                                    GError **perror)
+e_book_backend_file_create_contacts (EBookBackendSync *backend,
+                                     EDataBook *book,
+                                     GCancellable *cancellable,
+                                     const GSList *vcards,
+                                     GSList **added_contacts,
+                                     GError **perror)
 {
 	EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
 
-	if (do_create (bf, vcard, contact, perror)) {
+	if (do_create (bf, vcards, added_contacts, perror)) {
 		GError *error = NULL;
 
-		if (!e_book_backend_sqlitedb_add_contact (bf->priv->sqlitedb,
+		if (!e_book_backend_sqlitedb_add_contacts (bf->priv->sqlitedb,
 							  SQLITEDB_FOLDER_ID,
-							  *contact, FALSE, &error)) {
-			g_warning ("Failed to add contact to summary: %s", error->message);
+							  *added_contacts, FALSE, &error)) {
+			g_warning ("Failed to add contacts to summary: %s", error->message);
 			g_error_free (error);
 		}
 	}
@@ -1219,18 +1273,31 @@ e_book_backend_file_open (EBookBackendSync *backend,
 				(gpointer (*)(gpointer , gsize)) g_try_realloc,
 				g_free);
 
+		/* Make sure the database directory is created
+		   or env->open will fail */
+		if (!only_if_exists) {
+			if (!create_directory (dirname, perror)) {
+				g_warning ("failed to create directory at %s", dirname);
+				G_UNLOCK (global_env);
+				g_free (dirname);
+				g_free (filename);
+				return;
+			}
+		}
+
 		/*
-		 * We need either DB_INIT_CDB or DB_INIT_LOCK, because we will have
-		 * multiple threads reading and writing concurrently without
-		 * any locking above libdb.
+		 * DB_INIT_TXN enables transaction support. It requires DB_INIT_LOCK to
+		 * initialize the locking subsystem and DB_INIT_LOG for the logging
+		 * subsystem.
+		 *
+		 * DB_INIT_MPOOL enables the in-memory cache.
 		 *
-		 * DB_INIT_CDB enforces multiple reader/single writer by locking inside
-		 * the database. It is used instead of DB_INIT_LOCK because DB_INIT_LOCK
-		 * may deadlock, which would have to be called in a separate thread.
-		 * Considered too complicated for not enough gain (= concurrent writes)
-		 * at this point.
+		 * Note that we need either DB_INIT_CDB or DB_INIT_LOCK, because we will
+		 * have multiple threads reading and writing concurrently without
+		 * any locking above libdb. Right now DB_INIT_LOCK is used because
+		 * DB_INIT_TXN conflicts with DB_INIT_CDB.
 		 */
-		db_error = (*env->open) (env, NULL, DB_INIT_CDB | DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_THREAD, 0);
+		db_error = (*env->open) (env, dirname, DB_INIT_LOCK | DB_INIT_TXN | DB_INIT_LOG | DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_THREAD, 0);
 		if (db_error != 0) {
 			env->close (env, 0);
 			g_warning ("db_env_open failed with %s", db_strerror (db_error));
@@ -1257,7 +1324,7 @@ e_book_backend_file_open (EBookBackendSync *backend,
 		return;
 	}
 
-	db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_THREAD, 0666);
+	db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_THREAD | DB_AUTO_COMMIT, 0666);
 
 	if (db_error == DB_OLD_VERSION) {
 		db_error = e_db3_utils_upgrade_format (filename);
@@ -1280,7 +1347,7 @@ e_book_backend_file_open (EBookBackendSync *backend,
 			return;
 		}
 
-		db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_THREAD, 0666);
+		db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_THREAD | DB_AUTO_COMMIT, 0666);
 	}
 
 	if (db_error == 0) {
@@ -1296,7 +1363,7 @@ e_book_backend_file_open (EBookBackendSync *backend,
 			return;
 		}
 
-		db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_RDONLY | DB_THREAD, 0666);
+		db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_RDONLY | DB_THREAD | DB_AUTO_COMMIT, 0666);
 
 		if (db_error != 0 && !only_if_exists) {
 
@@ -1319,7 +1386,7 @@ e_book_backend_file_open (EBookBackendSync *backend,
 				return;
 			}
 
-			db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_CREATE | DB_THREAD, 0666);
+			db_error = (*db->open) (db, NULL, filename, NULL, DB_HASH, DB_CREATE | DB_THREAD | DB_AUTO_COMMIT, 0666);
 			if (db_error != 0) {
 				db->close (db, 0);
 				g_warning ("db->open (... %s ... DB_CREATE ...) failed with %s", filename, db_strerror (db_error));
@@ -1346,12 +1413,12 @@ e_book_backend_file_open (EBookBackendSync *backend,
 
 #ifdef CREATE_DEFAULT_VCARD
 	if (create_default_vcard) {
-		EContact *contact = NULL;
+		GSList l;
+		l.data = XIMIAN_VCARD;
+		l.next = NULL;
 
-		if (!do_create (bf, XIMIAN_VCARD, &contact, NULL))
+		if (!do_create (bf, &l, NULL, NULL))
 			g_warning ("Cannot create default contact");
-		if (contact)
-			g_object_unref (contact);
 	}
 #endif
 
@@ -1474,7 +1541,7 @@ e_book_backend_file_get_backend_property (EBookBackendSync *backend,
 	g_return_val_if_fail (prop_value != NULL, FALSE);
 
 	if (g_str_equal (prop_name, CLIENT_BACKEND_PROPERTY_CAPABILITIES)) {
-		*prop_value = g_strdup ("local,do-initial-query,bulk-removes,contact-lists");
+		*prop_value = g_strdup ("local,do-initial-query,bulk-adds,bulk-removes,contact-lists");
 	} else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_REQUIRED_FIELDS)) {
 		*prop_value = g_strdup (e_contact_field_name (E_CONTACT_FILE_AS));
 	} else if (g_str_equal (prop_name, BOOK_BACKEND_PROPERTY_SUPPORTED_FIELDS)) {
@@ -1680,7 +1747,7 @@ e_book_backend_file_class_init (EBookBackendFileClass *klass)
 	sync_class->open_sync			= e_book_backend_file_open;
 	sync_class->remove_sync			= e_book_backend_file_remove;
 	sync_class->get_backend_property_sync	= e_book_backend_file_get_backend_property;
-	sync_class->create_contact_sync		= e_book_backend_file_create_contact;
+	sync_class->create_contacts_sync	= e_book_backend_file_create_contacts;
 	sync_class->remove_contacts_sync	= e_book_backend_file_remove_contacts;
 	sync_class->modify_contact_sync		= e_book_backend_file_modify_contact;
 	sync_class->get_contact_sync		= e_book_backend_file_get_contact;
diff --git a/addressbook/backends/google/e-book-backend-google.c b/addressbook/backends/google/e-book-backend-google.c
index 1438959..c6f490d 100644
--- a/addressbook/backends/google/e-book-backend-google.c
+++ b/addressbook/backends/google/e-book-backend-google.c
@@ -1203,22 +1203,24 @@ create_contact_finish (CreateContactData *data,
                        GDataContactsContact *new_contact,
                        const GError *gdata_error)
 {
-	EContact *e_contact;
-
 	__debug__ (G_STRFUNC);
 
 	if (gdata_error == NULL) {
 		/* Add the new contact to the cache. If uploading the photo was successful, the photo's data is stored on the contact as the "photo"
 		 * key, which the cache will pick up and store. */
+		EContact *e_contact;
+		GSList added_contacts = {NULL,};
 		e_contact = cache_add_contact (data->backend, GDATA_ENTRY (new_contact));
-		e_data_book_respond_create (data->book, data->opid, NULL, e_contact);
+
+		added_contacts.data = e_contact;
+		e_data_book_respond_create_contacts (data->book, data->opid, NULL, &added_contacts);
 		g_object_unref (e_contact);
 	} else {
 		GError *book_error = NULL;
 
 		/* Report the error. */
 		data_book_error_from_gdata_error (&book_error, gdata_error);
-		e_data_book_respond_create (data->book, data->opid, book_error, NULL);
+		e_data_book_respond_create_contacts (data->book, data->opid, book_error, NULL);
 	}
 
 	finish_operation (data->backend, data->opid, gdata_error);
@@ -1367,24 +1369,35 @@ finish:
  * operation responded to in create_contact_photo_query_cb().
  */
 static void
-e_book_backend_google_create_contact (EBookBackend *backend,
-                                      EDataBook *book,
-                                      guint32 opid,
-                                      GCancellable *cancellable,
-                                      const gchar *vcard_str)
+e_book_backend_google_create_contacts (EBookBackend *backend,
+                                       EDataBook *book,
+                                       guint32 opid,
+                                       GCancellable *cancellable,
+                                       const GSList *vcards)
 {
 	EBookBackendGooglePrivate *priv = E_BOOK_BACKEND_GOOGLE (backend)->priv;
 	EContact *contact;
 	GDataEntry *entry;
 	gchar *xml;
 	CreateContactData *data;
+	const gchar *vcard_str = (const gchar *) vcards->data;
+
+	/* We make the assumption that the vCard list we're passed is always exactly one element long, since we haven't specified "bulk-adds"
+	 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
+	if (vcards->next != NULL) {
+		e_data_book_respond_create_contacts (book, opid,
+		                                     EDB_ERROR_EX (NOT_SUPPORTED,
+		                                     _("The backend does not support bulk additions")),
+		                                     NULL);
+		return;
+	}
 
 	__debug__ (G_STRFUNC);
 
 	__debug__ ("Creating: %s", vcard_str);
 
 	if (!e_backend_get_online (E_BACKEND (backend))) {
-		e_data_book_respond_create (book, opid, EDB_ERROR (OFFLINE_UNAVAILABLE), NULL);
+		e_data_book_respond_create_contacts (book, opid, EDB_ERROR (OFFLINE_UNAVAILABLE), NULL);
 		return;
 	}
 
@@ -1487,6 +1500,13 @@ e_book_backend_google_remove_contacts (EBookBackend *backend,
 
 	/* We make the assumption that the ID list we're passed is always exactly one element long, since we haven't specified "bulk-removes"
 	 * in our static capability list. This simplifies a lot of the logic, especially around asynchronous results. */
+	if (id_list->next != NULL) {
+		e_data_book_respond_remove_contacts (book, opid,
+		                                     EDB_ERROR_EX (NOT_SUPPORTED,
+		                                     _("The backend does not support bulk removals")),
+		                                     NULL);
+		return;
+	}
 	g_return_if_fail (!id_list->next);
 
 	/* Get the contact and associated GDataEntry from the cache */
@@ -2542,7 +2562,7 @@ e_book_backend_google_class_init (EBookBackendGoogleClass *klass)
 	backend_class->start_book_view		= e_book_backend_google_start_book_view;
 	backend_class->stop_book_view		= e_book_backend_google_stop_book_view;
 	backend_class->remove			= e_book_backend_google_remove;
-	backend_class->create_contact		= e_book_backend_google_create_contact;
+	backend_class->create_contacts		= e_book_backend_google_create_contacts;
 	backend_class->remove_contacts		= e_book_backend_google_remove_contacts;
 	backend_class->modify_contact		= e_book_backend_google_modify_contact;
 	backend_class->get_contact		= e_book_backend_google_get_contact;
diff --git a/addressbook/backends/ldap/e-book-backend-ldap.c b/addressbook/backends/ldap/e-book-backend-ldap.c
index 1412cfd..f10a294 100644
--- a/addressbook/backends/ldap/e-book-backend-ldap.c
+++ b/addressbook/backends/ldap/e-book-backend-ldap.c
@@ -1553,24 +1553,25 @@ create_contact_handler (LDAPOp *op,
 	EBookBackendLDAP *bl = E_BOOK_BACKEND_LDAP (op->backend);
 	gchar *ldap_error_msg;
 	gint ldap_error;
+	GSList added_contacts = {NULL,};
 
 	g_static_rec_mutex_lock (&eds_ldap_handler_lock);
 	if (!bl->priv->ldap) {
 		g_static_rec_mutex_unlock (&eds_ldap_handler_lock);
-		e_data_book_respond_create (op->book,
-					    op->opid,
-					    EDB_ERROR_NOT_CONNECTED (),
-					    NULL);
+		e_data_book_respond_create_contacts (op->book,
+					             op->opid,
+					             EDB_ERROR_NOT_CONNECTED (),
+					             NULL);
 		ldap_op_finished (op);
 		return;
 	}
 	g_static_rec_mutex_unlock (&eds_ldap_handler_lock);
 
 	if (LDAP_RES_ADD != ldap_msgtype (res)) {
-		e_data_book_respond_create (op->book,
-					    op->opid,
-					    EDB_ERROR_MSG_TYPE (ldap_msgtype (res)),
-					    NULL);
+		e_data_book_respond_create_contacts (op->book,
+					             op->opid,
+					             EDB_ERROR_MSG_TYPE (ldap_msgtype (res)),
+					             NULL);
 		ldap_op_finished (op);
 		return;
 	}
@@ -1590,10 +1591,11 @@ create_contact_handler (LDAPOp *op,
 	ldap_memfree (ldap_error_msg);
 
 	/* and lastly respond */
-	e_data_book_respond_create (op->book,
-				    op->opid,
-				    ldap_error_to_response (ldap_error),
-				    create_op->new_contact);
+	added_contacts.data = create_op->new_contact;
+	e_data_book_respond_create_contacts (op->book,
+				             op->opid,
+				             ldap_error_to_response (ldap_error),
+				             &added_contacts);
 
 	ldap_op_finished (op);
 }
@@ -1609,11 +1611,11 @@ create_contact_dtor (LDAPOp *op)
 }
 
 static void
-e_book_backend_ldap_create_contact (EBookBackend *backend,
-                                    EDataBook *book,
-                                    guint32 opid,
-                                    GCancellable *cancellable,
-                                    const gchar *vcard)
+e_book_backend_ldap_create_contacts (EBookBackend *backend,
+                                     EDataBook *book,
+                                     guint32 opid,
+                                     GCancellable *cancellable,
+                                     const GSList *vcards)
 {
 	LDAPCreateOp *create_op = g_new0 (LDAPCreateOp, 1);
 	EBookBackendLDAP *bl = E_BOOK_BACKEND_LDAP (backend);
@@ -1623,16 +1625,27 @@ e_book_backend_ldap_create_contact (EBookBackend *backend,
 	GPtrArray *mod_array;
 	LDAPMod **ldap_mods;
 	gchar *new_uid;
+	const gchar *vcard = (const gchar *) vcards->data;
+
+	/* We make the assumption that the vCard list we're passed is always exactly one element long, since we haven't specified "bulk-adds"
+	 * in our static capability list. This is because there is no clean way to roll back changes in case of an error. */
+	if (vcards->next != NULL) {
+		e_data_book_respond_create_contacts (book, opid,
+		                                     EDB_ERROR_EX (NOT_SUPPORTED,
+		                                     _("The backend does not support bulk additions")),
+		                                     NULL);
+		return;
+	}
 
 	if (!e_backend_get_online (E_BACKEND (backend))) {
-		e_data_book_respond_create (book, opid, EDB_ERROR (REPOSITORY_OFFLINE), NULL);
+		e_data_book_respond_create_contacts (book, opid, EDB_ERROR (REPOSITORY_OFFLINE), NULL);
 		return;
 	}
 
 	g_static_rec_mutex_lock (&eds_ldap_handler_lock);
 	if (!bl->priv->ldap) {
 		g_static_rec_mutex_unlock (&eds_ldap_handler_lock);
-		e_data_book_respond_create (book, opid, EDB_ERROR_NOT_CONNECTED (), NULL);
+		e_data_book_respond_create_contacts (book, opid, EDB_ERROR_NOT_CONNECTED (), NULL);
 		return;
 	}
 	g_static_rec_mutex_unlock (&eds_ldap_handler_lock);
@@ -1730,10 +1743,10 @@ e_book_backend_ldap_create_contact (EBookBackend *backend,
 	free_mods (mod_array);
 
 	if (LDAP_SUCCESS != err) {
-		e_data_book_respond_create (create_op->op.book,
-					    opid,
-					    ldap_error_to_response (err),
-					    NULL);
+		e_data_book_respond_create_contacts (create_op->op.book,
+					             opid,
+					             ldap_error_to_response (err),
+					             NULL);
 		create_contact_dtor ((LDAPOp *) create_op);
 		return;
 	} else {
@@ -5669,7 +5682,7 @@ e_book_backend_ldap_class_init (EBookBackendLDAPClass *klass)
 	parent_class->remove			= e_book_backend_ldap_remove;
 	parent_class->get_backend_property	= e_book_backend_ldap_get_backend_property;
 
-	parent_class->create_contact		= e_book_backend_ldap_create_contact;
+	parent_class->create_contacts		= e_book_backend_ldap_create_contacts;
 	parent_class->remove_contacts		= e_book_backend_ldap_remove_contacts;
 	parent_class->modify_contact		= e_book_backend_ldap_modify_contact;
 	parent_class->get_contact		= e_book_backend_ldap_get_contact;
diff --git a/addressbook/backends/vcf/e-book-backend-vcf.c b/addressbook/backends/vcf/e-book-backend-vcf.c
index fb84ec6..9318409 100644
--- a/addressbook/backends/vcf/e-book-backend-vcf.c
+++ b/addressbook/backends/vcf/e-book-backend-vcf.c
@@ -56,6 +56,7 @@
 #define d(x)
 
 #define EDB_ERROR(_code) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, NULL)
+#define EDB_ERROR_EX(_code, _msg) e_data_book_create_error (E_DATA_BOOK_STATUS_ ## _code, _msg)
 
 G_DEFINE_TYPE (EBookBackendVCF, e_book_backend_vcf, E_TYPE_BOOK_BACKEND_SYNC)
 
@@ -271,17 +272,30 @@ do_create (EBookBackendVCF *bvcf,
 }
 
 static void
-e_book_backend_vcf_create_contact (EBookBackendSync *backend,
+e_book_backend_vcf_create_contacts (EBookBackendSync *backend,
                                    EDataBook *book,
                                    GCancellable *cancellable,
-                                   const gchar *vcard,
-                                   EContact **contact,
+                                   const GSList *vcards,
+                                   GSList **added_contacts,
                                    GError **perror)
 {
+	EContact *contact = NULL;
 	EBookBackendVCF *bvcf = E_BOOK_BACKEND_VCF (backend);
+	const gchar *vcard = vcards->data;
+
+	/* We make the assumption that the vCard list we're passed is always exactly one element long, since we haven't specified "bulk-adds"
+	 * in our static capability list. */
+	if (vcards->next != NULL) {
+		g_propagate_error (perror,
+		                   EDB_ERROR_EX (NOT_SUPPORTED,
+		                   _("The backend does not support bulk additions")));
+		return;
+	}
 
-	*contact = do_create(bvcf, vcard, TRUE);
-	if (!*contact) {
+	contact = do_create(bvcf, vcard, TRUE);
+	if (added_contacts) {
+		*added_contacts = g_slist_append (*added_contacts, contact);
+	} else {
 		/* XXX need a different call status for this case, i
 		 * think */
 		g_propagate_error (perror, EDB_ERROR (CONTACT_NOT_FOUND));
@@ -301,6 +315,15 @@ e_book_backend_vcf_remove_contacts (EBookBackendSync *backend,
 	const gchar *id = id_list->data;
 	GList *elem;
 
+	/* We make the assumption that the ID list we're passed is always exactly one element long, since we haven't specified "bulk-removes"
+	 * in our static capability list. */
+	if (id_list->next != NULL) {
+		g_propagate_error (perror,
+		                   EDB_ERROR_EX (NOT_SUPPORTED,
+		                   _("The backend does not support bulk removals")));
+		return;
+	}
+
 	g_mutex_lock (bvcf->priv->mutex);
 	elem = g_hash_table_lookup (bvcf->priv->contacts, id);
 	if (!elem) {
@@ -734,7 +757,7 @@ e_book_backend_vcf_class_init (EBookBackendVCFClass *klass)
 
 	sync_class->open_sync			= e_book_backend_vcf_open;
 	sync_class->get_backend_property_sync	= e_book_backend_vcf_get_backend_property;
-	sync_class->create_contact_sync		= e_book_backend_vcf_create_contact;
+	sync_class->create_contacts_sync	= e_book_backend_vcf_create_contacts;
 	sync_class->remove_contacts_sync	= e_book_backend_vcf_remove_contacts;
 	sync_class->modify_contact_sync		= e_book_backend_vcf_modify_contact;
 	sync_class->get_contact_sync		= e_book_backend_vcf_get_contact;
diff --git a/addressbook/backends/webdav/e-book-backend-webdav.c b/addressbook/backends/webdav/e-book-backend-webdav.c
index a399441..2b0fc0d 100644
--- a/addressbook/backends/webdav/e-book-backend-webdav.c
+++ b/addressbook/backends/webdav/e-book-backend-webdav.c
@@ -301,21 +301,33 @@ webdav_handle_auth_request (EBookBackendWebdav *webdav)
 }
 
 static void
-e_book_backend_webdav_create_contact (EBookBackend *backend,
-                                      EDataBook *book,
-                                      guint32 opid,
-                                      GCancellable *cancellable,
-                                      const gchar *vcard)
+e_book_backend_webdav_create_contacts (EBookBackend *backend,
+                                       EDataBook *book,
+                                       guint32 opid,
+                                       GCancellable *cancellable,
+                                       const GSList *vcards)
 {
 	EBookBackendWebdav        *webdav = E_BOOK_BACKEND_WEBDAV (backend);
 	EBookBackendWebdavPrivate *priv   = webdav->priv;
 	EContact                  *contact;
 	gchar                     *uid;
 	guint                      status;
-	gchar			  *status_reason = NULL;
+	gchar                     *status_reason = NULL;
+	const gchar               *vcard = (const gchar *) vcards->data;
+	GSList                     added_contacts = {NULL,};
+
+	/* We make the assumption that the vCard list we're passed is always exactly one element long, since we haven't specified "bulk-adds"
+	 * in our static capability list. This is because there is no clean way to roll back changes in case of an error. */
+	if (vcards->next != NULL) {
+		e_data_book_respond_create_contacts (book, opid,
+		                                     EDB_ERROR_EX (NOT_SUPPORTED,
+		                                     _("The backend does not support bulk additions")),
+		                                     NULL);
+		return;
+	}
 
 	if (!e_backend_get_online (E_BACKEND (backend))) {
-		e_data_book_respond_create (book, opid, EDB_ERROR (REPOSITORY_OFFLINE), NULL);
+		e_data_book_respond_create_contacts (book, opid, EDB_ERROR (REPOSITORY_OFFLINE), NULL);
 		return;
 	}
 
@@ -323,7 +335,7 @@ e_book_backend_webdav_create_contact (EBookBackend *backend,
 	 * good enough for us */
 	uid = g_strdup_printf("%s%08X-%08X-%08X.vcf", priv->uri, rand(), rand(),
 			      rand ());
-			      
+
 	contact = e_contact_new_from_vcard_with_uid (vcard, uid);
 
 	/* kill revision field (might have been set by some other backend) */
@@ -333,12 +345,12 @@ e_book_backend_webdav_create_contact (EBookBackend *backend,
 	if (status != 201 && status != 204) {
 		g_object_unref (contact);
 		if (status == 401 || status == 407) {
-			e_data_book_respond_create (book, opid, webdav_handle_auth_request (webdav), NULL);
+			e_data_book_respond_create_contacts (book, opid, webdav_handle_auth_request (webdav), NULL);
 		} else {
-			e_data_book_respond_create (book, opid,
-					e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR,
-						_("Create resource '%s' failed with HTTP status: %d (%s)"), uid, status, status_reason),
-					NULL);
+			e_data_book_respond_create_contacts (book, opid,
+				        e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR,
+				        _("Create resource '%s' failed with HTTP status: %d (%s)"), uid, status, status_reason),
+				        NULL);
 		}
 		g_free (uid);
 		g_free (status_reason);
@@ -358,7 +370,7 @@ e_book_backend_webdav_create_contact (EBookBackend *backend,
 		g_object_unref (contact);
 
 		if (new_contact == NULL) {
-			e_data_book_respond_create (book, opid,
+			e_data_book_respond_create_contacts (book, opid,
 					EDB_ERROR (OTHER_ERROR), NULL);
 			g_free (uid);
 			return;
@@ -367,10 +379,11 @@ e_book_backend_webdav_create_contact (EBookBackend *backend,
 	}
 
 	e_book_backend_cache_add_contact (priv->cache, contact);
-	e_data_book_respond_create (book, opid, EDB_ERROR (SUCCESS), contact);
 
-	if (contact)
-		g_object_unref (contact);
+	added_contacts.data = contact;
+	e_data_book_respond_create_contacts (book, opid, EDB_ERROR (SUCCESS), &added_contacts);
+
+	g_object_unref (contact);
 	g_free (uid);
 }
 
@@ -449,7 +462,7 @@ e_book_backend_webdav_modify_contact (EBookBackend *backend,
 	gchar *status_reason = NULL;
 
 	if (!e_backend_get_online (E_BACKEND (backend))) {
-		e_data_book_respond_create (book, opid,
+		e_data_book_respond_create_contacts (book, opid,
 				EDB_ERROR (REPOSITORY_OFFLINE), NULL);
 		g_object_unref (contact);
 		return;
@@ -1444,7 +1457,7 @@ e_book_backend_webdav_class_init (EBookBackendWebdavClass *klass)
 	backend_class->open			= e_book_backend_webdav_open;
 	backend_class->get_backend_property	= e_book_backend_webdav_get_backend_property;
 
-	backend_class->create_contact		= e_book_backend_webdav_create_contact;
+	backend_class->create_contacts		= e_book_backend_webdav_create_contacts;
 	backend_class->remove_contacts		= e_book_backend_webdav_remove_contacts;
 	backend_class->modify_contact		= e_book_backend_webdav_modify_contact;
 	backend_class->get_contact		= e_book_backend_webdav_get_contact;
diff --git a/addressbook/libebook/e-book-client.c b/addressbook/libebook/e-book-client.c
index 90df80e..9d6acfd 100644
--- a/addressbook/libebook/e-book-client.c
+++ b/addressbook/libebook/e-book-client.c
@@ -1273,15 +1273,18 @@ e_book_client_add_contact (EBookClient *client,
                            gpointer user_data)
 {
 	gchar *vcard, *gdbus_vcard = NULL;
+	const gchar *strv[2];
 
 	g_return_if_fail (contact != NULL);
 	g_return_if_fail (E_IS_CONTACT (contact));
 
 	vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+	strv[0] = e_util_ensure_gdbus_string (vcard, &gdbus_vcard);
+	strv[1] = NULL;
 
-	e_client_proxy_call_string (E_CLIENT (client), e_util_ensure_gdbus_string (vcard, &gdbus_vcard), cancellable, callback, user_data, e_book_client_add_contact,
-			e_gdbus_book_call_add_contact,
-			NULL, NULL, e_gdbus_book_call_add_contact_finish, NULL, NULL);
+	e_client_proxy_call_strv (E_CLIENT (client), strv, cancellable, callback, user_data, e_book_client_add_contact,
+			e_gdbus_book_call_add_contacts,
+			NULL, NULL, NULL, e_gdbus_book_call_add_contacts_finish, NULL);
 
 	g_free (vcard);
 	g_free (gdbus_vcard);
@@ -1311,17 +1314,17 @@ e_book_client_add_contact_finish (EBookClient *client,
                                   GError **error)
 {
 	gboolean res;
-	gchar *out_uid = NULL;
+	gchar **out_uids = NULL;
 
-	res = e_client_proxy_call_finish_string (E_CLIENT (client), result, &out_uid, error, e_book_client_add_contact);
+	res = e_client_proxy_call_finish_strv (E_CLIENT (client), result, &out_uids, error, e_book_client_add_contact);
 
-	if (res && out_uid && added_uid) {
-		*added_uid = out_uid;
+	if (res && out_uids && added_uid) {
+		*added_uid = g_strdup (out_uids[0]);
 	} else {
-		g_free (out_uid);
 		if (added_uid)
 			*added_uid = NULL;
 	}
+	g_strfreev (out_uids);
 
 	return res;
 }
@@ -1353,7 +1356,8 @@ e_book_client_add_contact_sync (EBookClient *client,
                                 GError **error)
 {
 	gboolean res;
-	gchar *vcard, *gdbus_vcard = NULL, *out_uid = NULL;
+	gchar *vcard, *gdbus_vcard = NULL, **out_uids = NULL;
+	const gchar *strv[2];
 
 	g_return_val_if_fail (client != NULL, FALSE);
 	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
@@ -1365,17 +1369,19 @@ e_book_client_add_contact_sync (EBookClient *client,
 	}
 
 	vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+	strv[0] = e_util_ensure_gdbus_string (vcard, &gdbus_vcard);
+	strv[1] = NULL;
 
-	res = e_client_proxy_call_sync_string__string (E_CLIENT (client), e_util_ensure_gdbus_string (vcard, &gdbus_vcard), &out_uid, cancellable, error, e_gdbus_book_call_add_contact_sync);
+	res = e_client_proxy_call_sync_strv__strv (E_CLIENT (client), strv, &out_uids, cancellable, error, e_gdbus_book_call_add_contacts_sync);
 
-	if (res && out_uid && added_uid) {
-		*added_uid = out_uid;
+	if (res && out_uids && added_uid) {
+		*added_uid = g_strdup (out_uids[0]);
 	} else {
-		g_free (out_uid);
 		if (added_uid)
 			*added_uid = NULL;
 	}
 
+	g_strfreev (out_uids);
 	g_free (vcard);
 	g_free (gdbus_vcard);
 
@@ -1383,6 +1389,155 @@ e_book_client_add_contact_sync (EBookClient *client,
 }
 
 /**
+ * e_book_client_add_contacts:
+ * @client: an #EBookClient
+ * @contacts: a #GSList of #EContact objects to add
+ * @cancellable: a #GCancellable; can be %NULL
+ * @callback: callback to call when a result is ready
+ * @user_data: user data for the @callback
+ *
+ * Adds @contacts to @client.
+ * The call is finished by e_book_client_add_contacts_finish()
+ * from the @callback.
+ *
+ * Since: 3.4
+ **/
+void
+e_book_client_add_contacts (EBookClient *client,
+                            /* const */ GSList *contacts,
+                            GCancellable *cancellable,
+                            GAsyncReadyCallback callback,
+                            gpointer user_data)
+{
+	gchar **array;
+	const GSList *l;
+	gint i = 0;
+
+	g_return_if_fail (contacts != NULL);
+
+	array = g_new0 (gchar *, g_slist_length (contacts) + 1);
+	for (l = contacts; l != NULL; l = l->next) {
+		gchar *vcard = e_vcard_to_string (E_VCARD (l->data), EVC_FORMAT_VCARD_30);
+		array[i++] = e_util_utf8_make_valid (vcard);
+		g_free (vcard);
+	}
+
+	e_client_proxy_call_strv (E_CLIENT (client), (const gchar * const *) array, cancellable, callback, user_data, e_book_client_add_contacts,
+			e_gdbus_book_call_add_contacts,
+			NULL, NULL, NULL, e_gdbus_book_call_add_contacts_finish, NULL);
+
+	g_strfreev (array);
+}
+
+/**
+ * e_book_client_add_contacts_finish:
+ * @client: an #EBookClient
+ * @result: a #GAsyncResult
+ * @added_uids: (out): UIDs of newly added contacts; can be %NULL
+ * @error: (out): a #GError to set an error, if any
+ *
+ * Finishes previous call of e_book_client_add_contacts() and
+ * sets @added_uids to the UIDs of newly added contacts if successful.
+ * This #GSList should be freed with e_client_util_free_string_slist().
+ *
+ * If any of the contacts cannot be inserted, all of the insertions will be
+ * reverted and this method will return %FALSE.
+ *
+ * Note: This is not modifying original #EContact objects.
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise.
+ *
+ * Since: 3.4
+ **/
+gboolean
+e_book_client_add_contacts_finish (EBookClient *client,
+                                   GAsyncResult *result,
+                                   GSList **added_uids,
+                                   GError **error)
+{
+	gboolean res;
+	gchar **out_uids = NULL;
+
+	res = e_client_proxy_call_finish_strv (E_CLIENT (client), result, &out_uids, error, e_book_client_add_contacts);
+
+	if (res && out_uids && added_uids) {
+		*added_uids = e_client_util_strv_to_slist ((const gchar * const*) out_uids);
+	} else {
+		if (added_uids)
+			*added_uids = NULL;
+	}
+
+	g_strfreev (out_uids);
+
+	return res;
+}
+
+/**
+ * e_book_client_add_contacts_sync:
+ * @client: an #EBookClient
+ * @contacts: a #GSList of #EContact objects to add
+ * @added_uids: (out): UIDs of newly added contacts; can be %NULL
+ * @cancellable: a #GCancellable; can be %NULL
+ * @error: (out): a #GError to set an error, if any
+ *
+ * Adds @contacts to @client and
+ * sets @added_uids to the UIDs of newly added contacts if successful.
+ * This #GSList should be freed with e_client_util_free_string_slist().
+ *
+ * If any of the contacts cannot be inserted, all of the insertions will be
+ * reverted and this method will return %FALSE.
+ *
+ * Note: This is not modifying original @contacts, thus if it's needed,
+ * then use e_contact_set (contact, E_CONTACT_UID, new_uid).
+ *
+ * Returns: %TRUE if successful, %FALSE otherwise.
+ *
+ * Since: 3.4
+ **/
+gboolean
+e_book_client_add_contacts_sync	(EBookClient *client,
+                                 /* const */ GSList *contacts,
+                                 GSList **added_uids,
+                                 GCancellable *cancellable,
+                                 GError **error)
+{
+	gboolean res;
+	gint i = 0;
+	gchar **array, **out_uids = NULL;
+	const GSList *l;
+
+	g_return_val_if_fail (client != NULL, FALSE);
+	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
+	g_return_val_if_fail (client->priv != NULL, FALSE);
+
+	if (!client->priv->gdbus_book) {
+		set_proxy_gone_error (error);
+		return FALSE;
+	}
+
+	array = g_new0 (gchar *, g_slist_length (contacts) + 1);
+	for (l = contacts; l != NULL; l = l->next) {
+		gchar *vcard = e_vcard_to_string (E_VCARD (l->data), EVC_FORMAT_VCARD_30);
+		array[i++] = e_util_utf8_make_valid (vcard);
+		g_free (vcard);
+	}
+
+	res = e_client_proxy_call_sync_strv__strv (E_CLIENT (client), (const gchar * const*) array, &out_uids, cancellable, error, e_gdbus_book_call_add_contacts_sync);
+
+	if (res && out_uids && added_uids) {
+		*added_uids = e_client_util_strv_to_slist ((const gchar * const*) out_uids);
+	} else {
+		if (added_uids)
+			*added_uids = NULL;
+	}
+
+	g_strfreev (out_uids);
+	g_strfreev (array);
+
+	return res;
+}
+
+/**
  * e_book_client_modify_contact:
  * @client: an #EBookClient
  * @contact: an #EContact
@@ -1501,7 +1656,7 @@ e_book_client_remove_contact (EBookClient *client,
                               gpointer user_data)
 {
 	gchar *uid;
-	const gchar *lst[2];
+	const gchar *strv[2];
 
 	g_return_if_fail (contact != NULL);
 	g_return_if_fail (E_IS_CONTACT (contact));
@@ -1509,10 +1664,10 @@ e_book_client_remove_contact (EBookClient *client,
 	uid = e_util_utf8_make_valid (e_contact_get_const ((EContact *) contact, E_CONTACT_UID));
 	g_return_if_fail (uid != NULL);
 
-	lst[0] = uid;
-	lst[1] = NULL;
+	strv[0] = uid;
+	strv[1] = NULL;
 
-	e_client_proxy_call_strv (E_CLIENT (client), lst, cancellable, callback, user_data, e_book_client_remove_contact,
+	e_client_proxy_call_strv (E_CLIENT (client), strv, cancellable, callback, user_data, e_book_client_remove_contact,
 			e_gdbus_book_call_remove_contacts,
 			e_gdbus_book_call_remove_contacts_finish, NULL, NULL, NULL, NULL);
 
@@ -1560,7 +1715,7 @@ e_book_client_remove_contact_sync (EBookClient *client,
 {
 	gboolean res;
 	gchar *uid;
-	const gchar *lst[2];
+	const gchar *strv[2];
 
 	g_return_val_if_fail (client != NULL, FALSE);
 	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
@@ -1576,10 +1731,10 @@ e_book_client_remove_contact_sync (EBookClient *client,
 	uid = e_util_utf8_make_valid (e_contact_get_const ((EContact *) contact, E_CONTACT_UID));
 	g_return_val_if_fail (uid != NULL, 0);
 
-	lst[0] = uid;
-	lst[1] = NULL;
+	strv[0] = uid;
+	strv[1] = NULL;
 
-	res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), lst, cancellable, error, e_gdbus_book_call_remove_contacts_sync);
+	res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), strv, cancellable, error, e_gdbus_book_call_remove_contacts_sync);
 
 	g_free (uid);
 
@@ -1608,17 +1763,17 @@ e_book_client_remove_contact_by_uid (EBookClient *client,
                                      gpointer user_data)
 {
 	gchar *safe_uid;
-	const gchar *lst[2];
+	const gchar *strv[2];
 
 	g_return_if_fail (uid != NULL);
 
 	safe_uid = e_util_utf8_make_valid (uid);
 	g_return_if_fail (safe_uid != NULL);
 
-	lst[0] = safe_uid;
-	lst[1] = NULL;
+	strv[0] = safe_uid;
+	strv[1] = NULL;
 
-	e_client_proxy_call_strv (E_CLIENT (client), lst, cancellable, callback, user_data, e_book_client_remove_contact_by_uid,
+	e_client_proxy_call_strv (E_CLIENT (client), strv, cancellable, callback, user_data, e_book_client_remove_contact_by_uid,
 			e_gdbus_book_call_remove_contacts,
 			e_gdbus_book_call_remove_contacts_finish, NULL, NULL, NULL, NULL);
 
@@ -1666,7 +1821,7 @@ e_book_client_remove_contact_by_uid_sync (EBookClient *client,
 {
 	gboolean res;
 	gchar *safe_uid;
-	const gchar *lst[2];
+	const gchar *strv[2];
 
 	g_return_val_if_fail (client != NULL, FALSE);
 	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
@@ -1681,10 +1836,10 @@ e_book_client_remove_contact_by_uid_sync (EBookClient *client,
 	safe_uid = e_util_utf8_make_valid (uid);
 	g_return_val_if_fail (safe_uid != NULL, FALSE);
 
-	lst[0] = safe_uid;
-	lst[1] = NULL;
+	strv[0] = safe_uid;
+	strv[1] = NULL;
 
-	res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), lst, cancellable, error, e_gdbus_book_call_remove_contacts_sync);
+	res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), strv, cancellable, error, e_gdbus_book_call_remove_contacts_sync);
 
 	g_free (safe_uid);
 
@@ -1715,18 +1870,18 @@ e_book_client_remove_contacts (EBookClient *client,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
 {
-	gchar **lst;
+	gchar **strv;
 
 	g_return_if_fail (uids != NULL);
 
-	lst = e_client_util_slist_to_strv (uids);
-	g_return_if_fail (lst != NULL);
+	strv = e_client_util_slist_to_strv (uids);
+	g_return_if_fail (strv != NULL);
 
-	e_client_proxy_call_strv (E_CLIENT (client), (const gchar * const *) lst, cancellable, callback, user_data, e_book_client_remove_contacts,
+	e_client_proxy_call_strv (E_CLIENT (client), (const gchar * const *) strv, cancellable, callback, user_data, e_book_client_remove_contacts,
 			e_gdbus_book_call_remove_contacts,
 			e_gdbus_book_call_remove_contacts_finish, NULL, NULL, NULL, NULL);
 
-	g_strfreev (lst);
+	g_strfreev (strv);
 }
 
 /**
@@ -1772,7 +1927,7 @@ e_book_client_remove_contacts_sync (EBookClient *client,
                                     GError **error)
 {
 	gboolean res;
-	gchar **lst;
+	gchar **strv;
 
 	g_return_val_if_fail (client != NULL, FALSE);
 	g_return_val_if_fail (E_IS_BOOK_CLIENT (client), FALSE);
@@ -1784,12 +1939,12 @@ e_book_client_remove_contacts_sync (EBookClient *client,
 		return FALSE;
 	}
 
-	lst = e_client_util_slist_to_strv (uids);
-	g_return_val_if_fail (lst != NULL, FALSE);
+	strv = e_client_util_slist_to_strv (uids);
+	g_return_val_if_fail (strv != NULL, FALSE);
 
-	res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), (const gchar * const *) lst, cancellable, error, e_gdbus_book_call_remove_contacts_sync);
+	res = e_client_proxy_call_sync_strv__void (E_CLIENT (client), (const gchar * const *) strv, cancellable, error, e_gdbus_book_call_remove_contacts_sync);
 
-	g_strfreev (lst);
+	g_strfreev (strv);
 
 	return res;
 }
diff --git a/addressbook/libebook/e-book-client.h b/addressbook/libebook/e-book-client.h
index 1435ce8..5482bde 100644
--- a/addressbook/libebook/e-book-client.h
+++ b/addressbook/libebook/e-book-client.h
@@ -140,6 +140,10 @@ void		e_book_client_add_contact			(EBookClient *client, /* const */ EContact *co
 gboolean	e_book_client_add_contact_finish		(EBookClient *client, GAsyncResult *result, gchar **added_uid, GError **error);
 gboolean	e_book_client_add_contact_sync			(EBookClient *client, /* const */ EContact *contact, gchar **added_uid, GCancellable *cancellable, GError **error);
 
+void		e_book_client_add_contacts			(EBookClient *client, /* const */ GSList *contacts, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+gboolean	e_book_client_add_contacts_finish		(EBookClient *client, GAsyncResult *result, GSList **added_uids, GError **error);
+gboolean	e_book_client_add_contacts_sync			(EBookClient *client, /* const */ GSList *contacts, GSList **added_uids, GCancellable *cancellable, GError **error);
+
 void		e_book_client_modify_contact			(EBookClient *client, /* const */ EContact *contact, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
 gboolean	e_book_client_modify_contact_finish		(EBookClient *client, GAsyncResult *result, GError **error);
 gboolean	e_book_client_modify_contact_sync		(EBookClient *client, /* const */ EContact *contact, GCancellable *cancellable, GError **error);
diff --git a/addressbook/libebook/e-book.c b/addressbook/libebook/e-book.c
index 5f4f525..6598177 100644
--- a/addressbook/libebook/e-book.c
+++ b/addressbook/libebook/e-book.c
@@ -423,7 +423,8 @@ e_book_add_contact (EBook *book,
                     GError **error)
 {
 	GError *err = NULL;
-	gchar *vcard, *uid = NULL, *gdbus_vcard = NULL;
+	gchar *vcard, **uids = NULL, *gdbus_vcard = NULL;
+	const gchar *strv[2];
 
 	g_return_val_if_fail (E_IS_BOOK (book), FALSE);
 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
@@ -432,13 +433,16 @@ e_book_add_contact (EBook *book,
 		book->priv->gdbus_book, E_BOOK_ERROR_REPOSITORY_OFFLINE);
 
 	vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
-	e_gdbus_book_call_add_contact_sync (book->priv->gdbus_book, e_util_ensure_gdbus_string (vcard, &gdbus_vcard), &uid, NULL, &err);
+	strv[0] = e_util_ensure_gdbus_string (vcard, &gdbus_vcard);
+	strv[1] = NULL;
+
+	e_gdbus_book_call_add_contacts_sync (book->priv->gdbus_book, strv, &uids, NULL, &err);
 	g_free (vcard);
 	g_free (gdbus_vcard);
 
-	if (uid) {
-		e_contact_set (contact, E_CONTACT_UID, uid);
-		g_free (uid);
+	if (uids) {
+		e_contact_set (contact, E_CONTACT_UID, uids[0]);
+		g_strfreev (uids);
 	}
 
 	return unwrap_gerror (err, error);
@@ -450,12 +454,12 @@ add_contact_reply (GObject *gdbus_book,
                    gpointer user_data)
 {
 	GError *err = NULL, *error = NULL;
-	gchar *uid = NULL;
+	gchar *uid = NULL, **uids = NULL;
 	AsyncData *data = user_data;
 	EBookIdAsyncCallback excb = data->excallback;
 	EBookIdCallback cb = data->callback;
 
-	e_gdbus_book_call_add_contact_finish (G_DBUS_PROXY (gdbus_book), res, &uid, &error);
+	e_gdbus_book_call_add_contacts_finish (G_DBUS_PROXY (gdbus_book), res, &uids, &error);
 
 	unwrap_gerror (error, &err);
 
@@ -463,6 +467,8 @@ add_contact_reply (GObject *gdbus_book,
 	 * for the OUT values. This is bad. */
 	if (error)
 		uid = NULL;
+	else
+		uid = uids[0];
 
 	if (cb)
 		cb (data->book, err ? err->code : E_BOOK_ERROR_OK, uid, data->closure);
@@ -472,8 +478,8 @@ add_contact_reply (GObject *gdbus_book,
 	if (err)
 		g_error_free (err);
 
-	if (uid)
-		g_free (uid);
+	if (uids)
+		g_strfreev (uids);
 
 	g_object_unref (data->book);
 	g_slice_free (AsyncData, data);
@@ -500,6 +506,7 @@ e_book_async_add_contact (EBook *book,
 {
 	gchar *vcard, *gdbus_vcard = NULL;
 	AsyncData *data;
+	const gchar *strv[2];
 
 	g_return_val_if_fail (E_IS_BOOK (book), FALSE);
 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
@@ -508,13 +515,15 @@ e_book_async_add_contact (EBook *book,
 		book->priv->gdbus_book, E_BOOK_ERROR_REPOSITORY_OFFLINE);
 
 	vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+	strv[0] = e_util_ensure_gdbus_string (vcard, &gdbus_vcard);
+	strv[1] = NULL;
 
 	data = g_slice_new0 (AsyncData);
 	data->book = g_object_ref (book);
 	data->callback = cb;
 	data->closure = closure;
 
-	e_gdbus_book_call_add_contact (book->priv->gdbus_book, e_util_ensure_gdbus_string (vcard, &gdbus_vcard), NULL, add_contact_reply, data);
+	e_gdbus_book_call_add_contacts (book->priv->gdbus_book, strv, NULL, add_contact_reply, data);
 
 	g_free (vcard);
 	g_free (gdbus_vcard);
@@ -545,6 +554,7 @@ e_book_add_contact_async (EBook *book,
 {
 	gchar *vcard, *gdbus_vcard = NULL;
 	AsyncData *data;
+	const gchar *strv[2];
 
 	g_return_val_if_fail (E_IS_BOOK (book), FALSE);
 	g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
@@ -553,13 +563,15 @@ e_book_add_contact_async (EBook *book,
 		book->priv->gdbus_book, E_BOOK_ERROR_REPOSITORY_OFFLINE);
 
 	vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+	strv[0] = e_util_ensure_gdbus_string (vcard, &gdbus_vcard);
+	strv[1] = NULL;
 
 	data = g_slice_new0 (AsyncData);
 	data->book = g_object_ref (book);
 	data->excallback = cb;
 	data->closure = closure;
 
-	e_gdbus_book_call_add_contact (book->priv->gdbus_book, e_util_ensure_gdbus_string (vcard, &gdbus_vcard), NULL, add_contact_reply, data);
+	e_gdbus_book_call_add_contacts (book->priv->gdbus_book, strv, NULL, add_contact_reply, data);
 
 	g_free (vcard);
 	g_free (gdbus_vcard);
@@ -3755,13 +3767,13 @@ array_to_stringlist (gchar **list)
 }
 
 static EList *
-array_to_elist (gchar **list)
+array_to_elist (gchar **strv)
 {
 	EList *elst = NULL;
-	gchar **i = list;
+	gchar **i = strv;
 
 	elst = e_list_new (NULL, (EListFreeFunc) g_free, NULL);
-	if (!list)
+	if (!strv)
 		return elst;
 
 	while (*i != NULL) {
diff --git a/addressbook/libedata-book/e-book-backend-sync.c b/addressbook/libedata-book/e-book-backend-sync.c
index ecb386d..7ee5635 100644
--- a/addressbook/libedata-book/e-book-backend-sync.c
+++ b/addressbook/libedata-book/e-book-backend-sync.c
@@ -11,6 +11,7 @@
 #endif
 
 #include "e-book-backend-sync.h"
+#include "libedataserver/e-data-server-util.h"
 
 G_DEFINE_TYPE (EBookBackendSync, e_book_backend_sync, E_TYPE_BOOK_BACKEND)
 
@@ -61,31 +62,31 @@ e_book_backend_sync_open (EBookBackendSync *backend,
 }
 
 /**
- * e_book_backend_sync_create_contact:
+ * e_book_backend_sync_create_contacts:
  * @backend: an #EBookBackendSync
  * @book: an #EDataBook
  * @cancellable: a #GCancellable for the operation
- * @vcard: a VCard representation of a contact
- * @contact: a pointer to a location to store the resulting #EContact
+ * @vcards: a #GSList of vCard representations of contacts
+ * @added_contacts: a pointer to a location to store the resulting #EContact list
  * @error: #GError to set, when something fails
  *
- * Creates a new contact with the contents of @vcard in @backend.
+ * Creates new contacts with the contents of @vcards in @backend.
  **/
 void
-e_book_backend_sync_create_contact (EBookBackendSync *backend,
-                                    EDataBook *book,
-                                    GCancellable *cancellable,
-                                    const gchar *vcard,
-                                    EContact **contact,
-                                    GError **error)
+e_book_backend_sync_create_contacts (EBookBackendSync *backend,
+                                     EDataBook *book,
+                                     GCancellable *cancellable,
+                                     const GSList *vcards,
+                                     GSList **added_contacts,
+                                     GError **error)
 {
 	e_return_data_book_error_if_fail (E_IS_BOOK_BACKEND_SYNC (backend), E_DATA_BOOK_STATUS_INVALID_ARG);
 	e_return_data_book_error_if_fail (E_IS_DATA_BOOK (book), E_DATA_BOOK_STATUS_INVALID_ARG);
-	e_return_data_book_error_if_fail (vcard, E_DATA_BOOK_STATUS_INVALID_ARG);
-	e_return_data_book_error_if_fail (contact, E_DATA_BOOK_STATUS_INVALID_ARG);
-	e_return_data_book_error_if_fail (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->create_contact_sync, E_DATA_BOOK_STATUS_NOT_SUPPORTED);
+	e_return_data_book_error_if_fail (vcards, E_DATA_BOOK_STATUS_INVALID_ARG);
+	e_return_data_book_error_if_fail (added_contacts, E_DATA_BOOK_STATUS_INVALID_ARG);
+	e_return_data_book_error_if_fail (E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->create_contacts_sync, E_DATA_BOOK_STATUS_NOT_SUPPORTED);
 
-	(* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->create_contact_sync) (backend, book, cancellable, vcard, contact, error);
+	(* E_BOOK_BACKEND_SYNC_GET_CLASS (backend)->create_contacts_sync) (backend, book, cancellable, vcards, added_contacts, error);
 }
 
 /**
@@ -481,21 +482,20 @@ book_backend_set_backend_property (EBookBackend *backend,
 }
 
 static void
-book_backend_create_contact (EBookBackend *backend,
-                             EDataBook *book,
-                             guint32 opid,
-                             GCancellable *cancellable,
-                             const gchar *vcard)
+book_backend_create_contacts (EBookBackend *backend,
+                              EDataBook *book,
+                              guint32 opid,
+                              GCancellable *cancellable,
+                              const GSList *vcards)
 {
 	GError *error = NULL;
-	EContact *contact = NULL;
+	GSList *added_contacts = NULL;
 
-	e_book_backend_sync_create_contact (E_BOOK_BACKEND_SYNC (backend), book, cancellable, vcard, &contact, &error);
+	e_book_backend_sync_create_contacts (E_BOOK_BACKEND_SYNC (backend), book, cancellable, vcards, &added_contacts, &error);
 
-	e_data_book_respond_create (book, opid, error, contact);
+	e_data_book_respond_create_contacts (book, opid, error, added_contacts);
 
-	if (contact)
-		g_object_unref (contact);
+	e_util_free_object_slist (added_contacts);
 }
 
 static void
@@ -668,7 +668,7 @@ e_book_backend_sync_class_init (EBookBackendSyncClass *klass)
 	backend_class->refresh			= book_backend_refresh;
 	backend_class->get_backend_property	= book_backend_get_backend_property;
 	backend_class->set_backend_property	= book_backend_set_backend_property;
-	backend_class->create_contact		= book_backend_create_contact;
+	backend_class->create_contacts		= book_backend_create_contacts;
 	backend_class->remove_contacts		= book_backend_remove_contacts;
 	backend_class->modify_contact		= book_backend_modify_contact;
 	backend_class->get_contact		= book_backend_get_contact;
diff --git a/addressbook/libedata-book/e-book-backend-sync.h b/addressbook/libedata-book/e-book-backend-sync.h
index ec45ac2..621cdb3 100644
--- a/addressbook/libedata-book/e-book-backend-sync.h
+++ b/addressbook/libedata-book/e-book-backend-sync.h
@@ -33,7 +33,7 @@ struct _EBookBackendSyncClass {
 	void (* refresh_sync)			(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, GError **error);
 	gboolean (*get_backend_property_sync)	(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *prop_name, gchar **prop_value, GError **error);
 	gboolean (*set_backend_property_sync)	(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *prop_name, const gchar *prop_value, GError **error);
-	void (*create_contact_sync)		(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *vcard, EContact **contact, GError **error);
+	void (*create_contacts_sync)		(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const GSList *vcards, GSList **added_contacts, GError **error);
 	void (*remove_contacts_sync)		(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const GSList *id_list, GSList **removed_ids, GError **error);
 	void (*modify_contact_sync)		(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *vcard, EContact **contact, GError **error);
 	void (*get_contact_sync)		(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *id, gchar **vcard, GError **error);
@@ -52,7 +52,7 @@ void		e_book_backend_sync_remove		(EBookBackendSync *backend, EDataBook *book, G
 void		e_book_backend_sync_refresh		(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, GError **error);
 gboolean	e_book_backend_sync_get_backend_property (EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *prop_name, gchar **prop_value, GError **error);
 gboolean	e_book_backend_sync_set_backend_property (EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *prop_name, const gchar *prop_value, GError **error);
-void		e_book_backend_sync_create_contact	(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *vcard, EContact **contact, GError **error);
+void		e_book_backend_sync_create_contacts	(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const GSList *vcards, GSList **added_contacts, GError **error);
 void		e_book_backend_sync_remove_contacts	(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const GSList *id_list, GSList **removed_ids, GError **error);
 void		e_book_backend_sync_modify_contact	(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *vcard, EContact **contact, GError **error);
 void		e_book_backend_sync_get_contact		(EBookBackendSync *backend, EDataBook *book, GCancellable *cancellable, const gchar *id, gchar **vcard, GError **error);
diff --git a/addressbook/libedata-book/e-book-backend.c b/addressbook/libedata-book/e-book-backend.c
index cb68604..5b69194 100644
--- a/addressbook/libedata-book/e-book-backend.c
+++ b/addressbook/libedata-book/e-book-backend.c
@@ -429,35 +429,35 @@ e_book_backend_refresh (EBookBackend *backend,
 }
 
 /**
- * e_book_backend_create_contact:
+ * e_book_backend_create_contacts
  * @backend: an #EBookBackend
  * @book: an #EDataBook
  * @opid: the ID to use for this operation
  * @cancellable: a #GCancellable for the operation
- * @vcard: the VCard to add
+ * @vcards: a #GSList of vCards to add
  *
- * Executes a 'create contact' request specified by @opid on @book
+ * Executes a 'create contacts' request specified by @opid on @book
  * using @backend.
- * This might be finished with e_data_book_respond_create().
+ * This might be finished with e_data_book_respond_create_contacts().
  **/
 void
-e_book_backend_create_contact (EBookBackend *backend,
+e_book_backend_create_contacts (EBookBackend *backend,
                                EDataBook *book,
                                guint32 opid,
                                GCancellable *cancellable,
-                               const gchar *vcard)
+                               const GSList *vcards)
 {
 	g_return_if_fail (E_IS_BOOK_BACKEND (backend));
 	g_return_if_fail (E_IS_DATA_BOOK (book));
-	g_return_if_fail (vcard);
-	g_return_if_fail (E_BOOK_BACKEND_GET_CLASS (backend)->create_contact);
+	g_return_if_fail (vcards);
+	g_return_if_fail (E_BOOK_BACKEND_GET_CLASS (backend)->create_contacts);
 
 	if (e_book_backend_is_opening (backend))
-		e_data_book_respond_create (book, opid, EDB_OPENING_ERROR, NULL);
+		e_data_book_respond_create_contacts (book, opid, EDB_OPENING_ERROR, NULL);
 	else if (!e_book_backend_is_opened (backend))
-		e_data_book_respond_create (book, opid, EDB_NOT_OPENED_ERROR, NULL);
+		e_data_book_respond_create_contacts (book, opid, EDB_NOT_OPENED_ERROR, NULL);
 	else
-		(* E_BOOK_BACKEND_GET_CLASS (backend)->create_contact) (backend, book, opid, cancellable, vcard);
+		(* E_BOOK_BACKEND_GET_CLASS (backend)->create_contacts) (backend, book, opid, cancellable, vcards);
 }
 
 /**
diff --git a/addressbook/libedata-book/e-book-backend.h b/addressbook/libedata-book/e-book-backend.h
index fd209f1..7e047d9 100644
--- a/addressbook/libedata-book/e-book-backend.h
+++ b/addressbook/libedata-book/e-book-backend.h
@@ -140,7 +140,7 @@ struct _EBookBackendClass {
 	void	(* authenticate_user)		(EBookBackend *backend, GCancellable *cancellable, ECredentials *credentials);
 
 	void	(* refresh)			(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable);
-	void	(* create_contact)		(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const gchar *vcard);
+	void	(* create_contacts)		(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const GSList *vcards);
 	void	(* remove_contacts)		(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const GSList *id_list);
 	void	(* modify_contact)		(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const gchar *vcard);
 	void	(* get_contact)			(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const gchar *id);
@@ -177,7 +177,7 @@ void		e_book_backend_set_backend_property (EBookBackend *backend, EDataBook *boo
 void		e_book_backend_open		(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, gboolean only_if_exists);
 void		e_book_backend_remove		(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable);
 void		e_book_backend_refresh		(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable);
-void		e_book_backend_create_contact	(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const gchar *vcard);
+void		e_book_backend_create_contacts	(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const GSList *vcards);
 void		e_book_backend_remove_contacts	(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const GSList *id_list);
 void		e_book_backend_modify_contact	(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const gchar *vcard);
 void		e_book_backend_get_contact	(EBookBackend *backend, EDataBook *book, guint32 opid, GCancellable *cancellable, const gchar *id);
diff --git a/addressbook/libedata-book/e-data-book.c b/addressbook/libedata-book/e-data-book.c
index 35c8420..3c0b5d2 100644
--- a/addressbook/libedata-book/e-data-book.c
+++ b/addressbook/libedata-book/e-data-book.c
@@ -66,7 +66,7 @@ typedef enum {
 	OP_GET_CONTACTS,
 	OP_GET_CONTACTS_UIDS,
 	OP_AUTHENTICATE,
-	OP_ADD_CONTACT,
+	OP_ADD_CONTACTS,
 	OP_REMOVE_CONTACTS,
 	OP_MODIFY_CONTACT,
 	OP_GET_BACKEND_PROPERTY,
@@ -93,6 +93,7 @@ typedef struct {
 		/* OP_REMOVE_CONTACTS */
 		GSList *ids;
 		/* OP_ADD_CONTACT */
+		GSList *vcards;
 		/* OP_MODIFY_CONTACT */
 		gchar *vcard;
 		/* OP_GET_VIEW */
@@ -153,9 +154,9 @@ operation_thread (gpointer data,
 	case OP_OPEN:
 		e_book_backend_open (backend, op->book, op->id, op->cancellable, op->d.only_if_exists);
 		break;
-	case OP_ADD_CONTACT:
-		e_book_backend_create_contact (backend, op->book, op->id, op->cancellable, op->d.vcard);
-		g_free (op->d.vcard);
+	case OP_ADD_CONTACTS:
+		e_book_backend_create_contacts (backend, op->book, op->id, op->cancellable, op->d.vcards);
+		e_util_free_string_slist (op->d.vcards);
 		break;
 	case OP_GET_CONTACT:
 		e_book_backend_get_contact (backend, op->book, op->id, op->cancellable, op->d.uid);
@@ -175,8 +176,7 @@ operation_thread (gpointer data,
 		break;
 	case OP_REMOVE_CONTACTS:
 		e_book_backend_remove_contacts (backend, op->book, op->id, op->cancellable, op->d.ids);
-		g_slist_foreach (op->d.ids, (GFunc) g_free, NULL);
-		g_slist_free (op->d.ids);
+		e_util_free_string_slist (op->d.ids);
 		break;
 	case OP_REMOVE:
 		e_book_backend_remove (backend, op->book, op->id, op->cancellable);
@@ -614,14 +614,14 @@ impl_Book_get_contact_list_uids (EGdbusBook *object,
 }
 
 static gboolean
-impl_Book_add_contact (EGdbusBook *object,
+impl_Book_add_contacts (EGdbusBook *object,
                        GDBusMethodInvocation *invocation,
-                       const gchar *in_vcard,
+                       const gchar * const *in_vcards,
                        EDataBook *book)
 {
 	OperationData *op;
 
-	if (in_vcard == NULL || !*in_vcard) {
+	if (in_vcards == NULL || !*in_vcards) {
 		GError *error = e_data_book_create_error (E_DATA_BOOK_STATUS_INVALID_QUERY, NULL);
 		/* Translators: This is prefix to a detailed error message */
 		data_book_return_error (invocation, error, _("Cannot add contact: "));
@@ -629,10 +629,10 @@ impl_Book_add_contact (EGdbusBook *object,
 		return TRUE;
 	}
 
-	op = op_new (OP_ADD_CONTACT, book);
-	op->d.vcard = g_strdup (in_vcard);
+	op = op_new (OP_ADD_CONTACTS, book);
+	op->d.vcards = e_util_strv_to_slist (in_vcards);
 
-	e_gdbus_book_complete_add_contact (book->priv->gdbus_object, invocation, op->id);
+	e_gdbus_book_complete_add_contacts (book->priv->gdbus_object, invocation, op->id);
 	e_operation_pool_push (ops_pool, op);
 
 	return TRUE;
@@ -1013,25 +1013,39 @@ e_data_book_respond_get_contact_list_uids (EDataBook *book,
 }
 
 void
-e_data_book_respond_create (EDataBook *book,
-                            guint32 opid,
-                            GError *error,
-                            const EContact *contact)
+e_data_book_respond_create_contacts (EDataBook *book,
+                                     guint32 opid,
+                                     GError *error,
+                                     const GSList *contacts)
 {
-	gchar *gdbus_uid = NULL;
+	gchar **array = NULL;
+	const GSList *l;
+	gint i = 0;
 
 	op_complete (book, opid);
 
+	array = g_new0 (gchar *, g_slist_length ((GSList *) contacts) + 1);
+	for (l = contacts; l != NULL; l = l->next) {
+		EContact *contact = E_CONTACT (l->data);
+
+		array[i++] = e_util_utf8_make_valid (e_contact_get_const (contact, E_CONTACT_UID));
+	}
+
 	/* Translators: This is prefix to a detailed error message */
 	g_prefix_error (&error, "%s", _("Cannot add contact: "));
 
-	e_gdbus_book_emit_add_contact_done (book->priv->gdbus_object, opid, error, e_util_ensure_gdbus_string (e_contact_get_const ((EContact *) contact, E_CONTACT_UID), &gdbus_uid));
+	e_gdbus_book_emit_add_contacts_done (book->priv->gdbus_object, opid, error, (const gchar * const *) array);
 
-	g_free (gdbus_uid);
+	g_strfreev (array);
 	if (error) {
 		g_error_free (error);
 	} else {
-		e_book_backend_notify_update (e_data_book_get_backend (book), contact);
+		for (l = contacts; l != NULL; l = l->next) {
+			EContact *contact = E_CONTACT (l->data);
+
+			e_book_backend_notify_update (e_data_book_get_backend (book), contact);
+		}
+
 		e_book_backend_notify_complete (e_data_book_get_backend (book));
 	}
 }
@@ -1376,8 +1390,8 @@ e_data_book_init (EDataBook *ebook)
 		gdbus_object, "handle-authenticate-user",
 		G_CALLBACK (impl_Book_authenticate_user), ebook);
 	g_signal_connect (
-		gdbus_object, "handle-add-contact",
-		G_CALLBACK (impl_Book_add_contact), ebook);
+		gdbus_object, "handle-add-contacts",
+		G_CALLBACK (impl_Book_add_contacts), ebook);
 	g_signal_connect (
 		gdbus_object, "handle-remove-contacts",
 		G_CALLBACK (impl_Book_remove_contacts), ebook);
diff --git a/addressbook/libedata-book/e-data-book.h b/addressbook/libedata-book/e-data-book.h
index 001f797..0b900c5 100644
--- a/addressbook/libedata-book/e-data-book.h
+++ b/addressbook/libedata-book/e-data-book.h
@@ -136,7 +136,7 @@ void		e_data_book_respond_remove			(EDataBook *book, guint32 opid, GError *error
 void		e_data_book_respond_refresh			(EDataBook *book, guint32 opid, GError *error);
 void		e_data_book_respond_get_backend_property	(EDataBook *book, guint32 opid, GError *error, const gchar *prop_value);
 void		e_data_book_respond_set_backend_property	(EDataBook *book, guint32 opid, GError *error);
-void		e_data_book_respond_create			(EDataBook *book, guint32 opid, GError *error, const EContact *contact);
+void		e_data_book_respond_create_contacts		(EDataBook *book, guint32 opid, GError *error, const GSList *contacts);
 void		e_data_book_respond_remove_contacts		(EDataBook *book, guint32 opid, GError *error, const GSList *ids);
 void		e_data_book_respond_modify			(EDataBook *book, guint32 opid, GError *error, const EContact *contact);
 void		e_data_book_respond_get_contact			(EDataBook *book, guint32 opid, GError *error, const gchar *vcard);
diff --git a/addressbook/libegdbus/e-gdbus-book.c b/addressbook/libegdbus/e-gdbus-book.c
index 04bf496..7b0f5fa 100644
--- a/addressbook/libegdbus/e-gdbus-book.c
+++ b/addressbook/libegdbus/e-gdbus-book.c
@@ -53,8 +53,8 @@ enum
 	__GET_CONTACT_LIST_DONE_SIGNAL,
 	__GET_CONTACT_LIST_UIDS_METHOD,
 	__GET_CONTACT_LIST_UIDS_DONE_SIGNAL,
-	__ADD_CONTACT_METHOD,
-	__ADD_CONTACT_DONE_SIGNAL,
+	__ADD_CONTACTS_METHOD,
+	__ADD_CONTACTS_DONE_SIGNAL,
 	__REMOVE_CONTACTS_METHOD,
 	__REMOVE_CONTACTS_DONE_SIGNAL,
 	__MODIFY_CONTACT_METHOD,
@@ -139,8 +139,8 @@ E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_STRV (GDBUS_BOOK_INTERFACE_NAME,
                                                       get_contact_list)
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_STRV (GDBUS_BOOK_INTERFACE_NAME,
                                                       get_contact_list_uids)
-E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_STRING (GDBUS_BOOK_INTERFACE_NAME,
-                                                        add_contact)
+E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_STRV (GDBUS_BOOK_INTERFACE_NAME,
+                                                        add_contacts)
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_VOID (GDBUS_BOOK_INTERFACE_NAME,
                                                       remove_contacts)
 E_DECLARE_GDBUS_METHOD_DONE_EMISSION_HOOK_ASYNC_VOID (GDBUS_BOOK_INTERFACE_NAME,
@@ -176,7 +176,7 @@ e_gdbus_book_default_init (EGdbusBookIface *iface)
 	E_INIT_GDBUS_METHOD_ASYNC_STRING__STRING(EGdbusBookIface, "get_contact",		get_contact, __GET_CONTACT_METHOD, __GET_CONTACT_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRING__STRV	(EGdbusBookIface, "get_contact_list",		get_contact_list, __GET_CONTACT_LIST_METHOD, __GET_CONTACT_LIST_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRING__STRV	(EGdbusBookIface, "get_contact_list_uids",	get_contact_list_uids, __GET_CONTACT_LIST_UIDS_METHOD, __GET_CONTACT_LIST_UIDS_DONE_SIGNAL)
-	E_INIT_GDBUS_METHOD_ASYNC_STRING__STRING(EGdbusBookIface, "add_contact",		add_contact, __ADD_CONTACT_METHOD, __ADD_CONTACT_DONE_SIGNAL)
+	E_INIT_GDBUS_METHOD_ASYNC_STRV__STRV    (EGdbusBookIface, "add_contacts",		add_contacts, __ADD_CONTACTS_METHOD, __ADD_CONTACTS_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRV__VOID	(EGdbusBookIface, "remove_contacts",		remove_contacts, __REMOVE_CONTACTS_METHOD, __REMOVE_CONTACTS_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRING__VOID	(EGdbusBookIface, "modify_contact",		modify_contact, __MODIFY_CONTACT_METHOD, __MODIFY_CONTACT_DONE_SIGNAL)
 	E_INIT_GDBUS_METHOD_ASYNC_STRING__STRING(EGdbusBookIface, "get_backend_property",	get_backend_property, __GET_BACKEND_PROPERTY_METHOD, __GET_BACKEND_PROPERTY_DONE_SIGNAL)
@@ -365,34 +365,34 @@ e_gdbus_book_call_get_contact_list_uids_sync (GDBusProxy *proxy,
 }
 
 void
-e_gdbus_book_call_add_contact (GDBusProxy *proxy,
-                               const gchar *in_vcard,
+e_gdbus_book_call_add_contacts (GDBusProxy *proxy,
+                               const gchar * const *in_vcards,
                                GCancellable *cancellable,
                                GAsyncReadyCallback callback,
                                gpointer user_data)
 {
-	e_gdbus_proxy_call_string ("add_contact", e_gdbus_book_call_add_contact, E_GDBUS_ASYNC_OP_KEEPER (proxy), in_vcard, cancellable, callback, user_data);
+	e_gdbus_proxy_call_strv ("add_contacts", e_gdbus_book_call_add_contacts, E_GDBUS_ASYNC_OP_KEEPER (proxy), in_vcards, cancellable, callback, user_data);
 }
 
 gboolean
-e_gdbus_book_call_add_contact_finish (GDBusProxy *proxy,
+e_gdbus_book_call_add_contacts_finish (GDBusProxy *proxy,
                                       GAsyncResult *result,
-                                      gchar **out_uid,
+                                      gchar ***out_uids,
                                       GError **error)
 {
-	return e_gdbus_proxy_finish_call_string (E_GDBUS_ASYNC_OP_KEEPER (proxy), result, out_uid, error, e_gdbus_book_call_add_contact);
+	return e_gdbus_proxy_finish_call_strv (E_GDBUS_ASYNC_OP_KEEPER (proxy), result, out_uids, error, e_gdbus_book_call_add_contacts);
 }
 
 gboolean
-e_gdbus_book_call_add_contact_sync (GDBusProxy *proxy,
-                                    const gchar *in_vcard,
-                                    gchar **out_uid,
+e_gdbus_book_call_add_contacts_sync (GDBusProxy *proxy,
+                                    const gchar * const *in_vcards,
+                                    gchar ***out_uids,
                                     GCancellable *cancellable,
                                     GError **error)
 {
-	return e_gdbus_proxy_call_sync_string__string (proxy, in_vcard, out_uid, cancellable, error,
-		e_gdbus_book_call_add_contact,
-		e_gdbus_book_call_add_contact_finish);
+	return e_gdbus_proxy_call_sync_strv__strv (proxy, in_vcards, out_uids, cancellable, error,
+		e_gdbus_book_call_add_contacts,
+		e_gdbus_book_call_add_contacts_finish);
 }
 
 void
@@ -694,9 +694,9 @@ DECLARE_EMIT_DONE_SIGNAL_1 (get_contact_list,
 DECLARE_EMIT_DONE_SIGNAL_1 (get_contact_list_uids,
                             __GET_CONTACT_LIST_UIDS_DONE_SIGNAL,
                             const gchar * const *)
-DECLARE_EMIT_DONE_SIGNAL_1 (add_contact,
-                            __ADD_CONTACT_DONE_SIGNAL,
-                            const gchar *)
+DECLARE_EMIT_DONE_SIGNAL_1 (add_contacts,
+                            __ADD_CONTACTS_DONE_SIGNAL,
+                            const gchar * const *)
 DECLARE_EMIT_DONE_SIGNAL_0 (remove_contacts,
                             __REMOVE_CONTACTS_DONE_SIGNAL)
 DECLARE_EMIT_DONE_SIGNAL_0 (modify_contact,
@@ -770,7 +770,7 @@ E_DECLARE_GDBUS_ASYNC_METHOD_0 (book,
 E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(book, get_contact, uid, "s", vcard, "s")
 E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(book, get_contact_list, query, "s", vcards, "as")
 E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(book, get_contact_list_uids, query, "s", uids, "as")
-E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(book, add_contact, vcard, "s", uid, "s")
+E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(book, add_contacts, vcards, "as", uids, "as")
 E_DECLARE_GDBUS_ASYNC_METHOD_1			(book, remove_contacts, list, "as")
 E_DECLARE_GDBUS_ASYNC_METHOD_1			(book, modify_contact, vcard, "s")
 E_DECLARE_GDBUS_ASYNC_METHOD_1_WITH_RETURN	(book, get_backend_property, prop_name, "s", prop_value, "s")
@@ -792,7 +792,7 @@ static const GDBusMethodInfo * const e_gdbus_book_method_info_pointers[] =
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (book, get_contact),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (book, get_contact_list),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (book, get_contact_list_uids),
-	&E_DECLARED_GDBUS_METHOD_INFO_NAME (book, add_contact),
+	&E_DECLARED_GDBUS_METHOD_INFO_NAME (book, add_contacts),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (book, remove_contacts),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (book, modify_contact),
 	&E_DECLARED_GDBUS_METHOD_INFO_NAME (book, get_backend_property),
@@ -820,7 +820,7 @@ static const GDBusSignalInfo * const e_gdbus_book_signal_info_pointers[] =
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, get_contact_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, get_contact_list_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, get_contact_list_uids_done),
-	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, add_contact_done),
+	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, add_contacts_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, remove_contacts_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, modify_contact_done),
 	&E_DECLARED_GDBUS_SIGNAL_INFO_NAME (book, get_backend_property_done),
@@ -1042,7 +1042,7 @@ e_gdbus_book_proxy_init (EGdbusBookProxy *proxy)
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRING (get_contact);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRV   (get_contact_list);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRV   (get_contact_list_uids);
-	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRING (add_contact);
+	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRV   (add_contacts);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_VOID   (remove_contacts);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_VOID   (modify_contact);
 	E_GDBUS_CONNECT_METHOD_DONE_SIGNAL_STRING (get_backend_property);
diff --git a/addressbook/libegdbus/e-gdbus-book.h b/addressbook/libegdbus/e-gdbus-book.h
index aea23ef..417c1f1 100644
--- a/addressbook/libegdbus/e-gdbus-book.h
+++ b/addressbook/libegdbus/e-gdbus-book.h
@@ -138,8 +138,8 @@ struct _EGdbusBookIface
 	gboolean (*handle_get_contact_list_uids)(EGdbusBook *object, GDBusMethodInvocation *invocation, const gchar *in_query);
 	void	 (*get_contact_list_uids_done)	(EGdbusBook *object, guint arg_opid, const GError *arg_error, gchar ***out_uids);
 
-	gboolean (*handle_add_contact)		(EGdbusBook *object, GDBusMethodInvocation *invocation, const gchar *in_vcard);
-	void	 (*add_contact_done)		(EGdbusBook *object, guint arg_opid, const GError *arg_error, gchar **out_uid);
+	gboolean (*handle_add_contacts)		(EGdbusBook *object, GDBusMethodInvocation *invocation, const gchar * const *in_vcards);
+	void	 (*add_contacts_done)		(EGdbusBook *object, guint arg_opid, const GError *arg_error, gchar ***out_uids);
 
 	gboolean (*handle_remove_contacts)	(EGdbusBook *object, GDBusMethodInvocation *invocation, const gchar * const *in_list);
 	void	 (*remove_contacts_done)	(EGdbusBook *object, guint arg_opid, const GError *arg_error);
@@ -190,9 +190,9 @@ void		e_gdbus_book_call_get_contact_list_uids (GDBusProxy *proxy, const gchar *i
 gboolean	e_gdbus_book_call_get_contact_list_uids_finish (GDBusProxy *proxy, GAsyncResult *result, gchar ***out_uids, GError **error);
 gboolean	e_gdbus_book_call_get_contact_list_uids_sync (GDBusProxy *proxy, const gchar *in_query, gchar ***out_uids, GCancellable *cancellable, GError **error);
 
-void		e_gdbus_book_call_add_contact (GDBusProxy *proxy, const gchar *in_vcard, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
-gboolean	e_gdbus_book_call_add_contact_finish (GDBusProxy *proxy, GAsyncResult *result, gchar **out_uid, GError **error);
-gboolean	e_gdbus_book_call_add_contact_sync (GDBusProxy *proxy, const gchar *in_vcard, gchar **out_uid, GCancellable *cancellable, GError **error);
+void		e_gdbus_book_call_add_contacts (GDBusProxy *proxy, const gchar * const *in_vcards, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+gboolean	e_gdbus_book_call_add_contacts_finish (GDBusProxy *proxy, GAsyncResult *result, gchar ***out_uids, GError **error);
+gboolean	e_gdbus_book_call_add_contacts_sync (GDBusProxy *proxy, const gchar * const *in_vcards, gchar ***out_uids, GCancellable *cancellable, GError **error);
 
 void		e_gdbus_book_call_remove_contacts (GDBusProxy *proxy, const gchar * const *in_list, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
 gboolean	e_gdbus_book_call_remove_contacts_finish (GDBusProxy *proxy, GAsyncResult *result, GError **error);
@@ -239,7 +239,7 @@ gboolean	e_gdbus_book_call_close_sync (GDBusProxy *proxy, GCancellable *cancella
 #define e_gdbus_book_complete_get_contact			e_gdbus_complete_async_method
 #define e_gdbus_book_complete_get_contact_list			e_gdbus_complete_async_method
 #define e_gdbus_book_complete_get_contact_list_uids		e_gdbus_complete_async_method
-#define e_gdbus_book_complete_add_contact			e_gdbus_complete_async_method
+#define e_gdbus_book_complete_add_contacts			e_gdbus_complete_async_method
 #define e_gdbus_book_complete_remove_contacts			e_gdbus_complete_async_method
 #define e_gdbus_book_complete_modify_contact			e_gdbus_complete_async_method
 #define e_gdbus_book_complete_get_backend_property		e_gdbus_complete_async_method
@@ -256,7 +256,7 @@ void e_gdbus_book_emit_refresh_done			(EGdbusBook *object, guint arg_opid, const
 void e_gdbus_book_emit_get_contact_done			(EGdbusBook *object, guint arg_opid, const GError *arg_error, const gchar *out_vcard);
 void e_gdbus_book_emit_get_contact_list_done		(EGdbusBook *object, guint arg_opid, const GError *arg_error, const gchar * const *out_vcards);
 void e_gdbus_book_emit_get_contact_list_uids_done	(EGdbusBook *object, guint arg_opid, const GError *arg_error, const gchar * const *out_uids);
-void e_gdbus_book_emit_add_contact_done			(EGdbusBook *object, guint arg_opid, const GError *arg_error, const gchar *out_uid);
+void e_gdbus_book_emit_add_contacts_done		(EGdbusBook *object, guint arg_opid, const GError *arg_error, const gchar *const *out_uids);
 void e_gdbus_book_emit_remove_contacts_done		(EGdbusBook *object, guint arg_opid, const GError *arg_error);
 void e_gdbus_book_emit_modify_contact_done		(EGdbusBook *object, guint arg_opid, const GError *arg_error);
 void e_gdbus_book_emit_get_backend_property_done	(EGdbusBook *object, guint arg_opid, const GError *arg_error, const gchar *out_prop_value);



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