[evolution-data-server/openismus-work] Work in progress: Implementing policy to store photos as uris



commit 51808ef70651f974222270d36f066b00fc62f30d
Author: Tristan Van Berkom <tristan van berkom gmail com>
Date:   Sat Jul 2 18:34:55 2011 -0400

    Work in progress: Implementing policy to store photos as uris

 addressbook/backends/file/e-book-backend-file.c   |  409 +++++++++++++---
 addressbook/libebook/e-book-view.c                |   28 +
 addressbook/libedata-book/Makefile.am             |    6 +-
 addressbook/libedata-book/e-book-backend.h        |    7 +-
 addressbook/libedata-book/e-data-book-types.h     |    8 +
 addressbook/libedata-book/e-data-book-view.c      |  542 +++++++++++++++++----
 addressbook/libedata-book/e-data-book-view.h      |   13 +
 addressbook/libegdbus/e-gdbus-egdbusbookview.c    |  145 +++++-
 addressbook/libegdbus/e-gdbus-egdbusbookview.h    |   19 +
 addressbook/tests/ebook/test-ebook-photo-is-uri.c |   25 +-
 10 files changed, 1020 insertions(+), 182 deletions(-)
---
diff --git a/addressbook/backends/file/e-book-backend-file.c b/addressbook/backends/file/e-book-backend-file.c
index 2c50426..f09d240 100644
--- a/addressbook/backends/file/e-book-backend-file.c
+++ b/addressbook/backends/file/e-book-backend-file.c
@@ -78,6 +78,8 @@ struct _EBookBackendFilePrivate {
 	DB_ENV   *env;
 	EBookBackendSummary *summary;
 	guint     id;
+	GHashTable *delete_pool;
+	GList      *views;
 };
 
 typedef enum {
@@ -92,6 +94,14 @@ typedef enum {
 	STATUS_ERROR
 } PhotoModifiedStatus;
 
+typedef struct {
+	gchar  *id;
+	GArray *uris;
+
+	GList  *expected_confirmations;
+} ContactDeleteInfo;
+
+
 G_LOCK_DEFINE_STATIC (global_env);
 static struct {
 	gint ref_count;
@@ -99,6 +109,24 @@ static struct {
 } global_env;
 
 static gboolean
+remove_file (const gchar *filename, GError **error)
+{
+	if (-1 == g_unlink (filename)) {
+		if (errno == EACCES || errno == EPERM) {
+			g_propagate_error (error, EDB_ERROR (PERMISSION_DENIED));
+		} else {
+			g_propagate_error (error, e_data_book_create_error_fmt
+					   (E_DATA_BOOK_STATUS_OTHER_ERROR, 
+					    "Failed to remove file '%s': %s", 
+					    filename, g_strerror (errno)));
+		}
+		return FALSE;
+	}
+
+	return TRUE;
+}
+
+static gboolean
 create_directory (const gchar *dirname,
 		  GError     **error)
 {
@@ -119,6 +147,290 @@ create_directory (const gchar *dirname,
 	return TRUE;
 }
 
+
+static void
+db_error_to_gerror (const gint db_error, GError **perror)
+{
+	if (db_error && perror && *perror)
+		g_clear_error (perror);
+
+	switch (db_error) {
+	case 0:
+		return;
+	case DB_NOTFOUND:
+		g_propagate_error (perror, EDB_ERROR (CONTACT_NOT_FOUND));
+		return;
+	case EACCES:
+		g_propagate_error (perror, EDB_ERROR (PERMISSION_DENIED));
+		return;
+	default:
+		g_propagate_error (perror, e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR, "db error 0x%x (%s)", db_error, db_strerror (db_error) ? db_strerror (db_error) : "Unknown error"));
+		return;
+	}
+}
+
+static void
+string_to_dbt(const gchar *str, DBT *dbt)
+{
+	memset (dbt, 0, sizeof (*dbt));
+	dbt->data = (gpointer)str;
+	dbt->size = strlen (str) + 1;
+	dbt->flags = DB_DBT_USERMEM;
+}
+
+static EContact*
+create_contact (const gchar *uid, const gchar *vcard)
+{
+	EContact *contact = e_contact_new_from_vcard (vcard);
+	if (!e_contact_get_const (contact, E_CONTACT_UID))
+		e_contact_set (contact, E_CONTACT_UID, uid);
+
+	return contact;
+}
+
+static ContactDeleteInfo *
+delete_info_new (const gchar *id)
+{
+	ContactDeleteInfo *delete_info = g_slice_new0 (ContactDeleteInfo);
+
+	delete_info->id   = g_strdup (id);
+	delete_info->uris = g_array_new (TRUE, TRUE, sizeof (gchar *));
+
+	return delete_info;
+}
+
+static void
+delete_info_finish (ContactDeleteInfo *delete_info)
+{
+	GError *error = NULL;
+	gint i;
+
+	for (i = 0; i < delete_info->uris->len; i++) {
+		gchar *uri = g_array_index (delete_info->uris, gchar *, i);
+		gchar *filename;
+
+		g_message ("Removing uri: %s", uri);
+
+		if ((filename = g_filename_from_uri (uri, NULL, &error)) != NULL) {
+
+			if (!remove_file (filename, &error)) {
+				g_warning ("Unable to remove image file %s: %s", filename, error->message);
+				g_clear_error (&error);
+			}
+
+			g_free (filename);
+		} else {
+			g_warning ("Unable to create filename from uri %s: %s", uri, error->message);
+			g_clear_error (&error);
+		}
+		g_free (uri);
+	}
+
+	g_array_free (delete_info->uris, TRUE);
+	g_free (delete_info->id);
+	g_slice_free (ContactDeleteInfo, delete_info);
+}
+
+static void
+backend_enable_contact_notifications (EBookBackendFile *bf,
+				      const gchar      *id,
+				      gboolean          enabled)
+{
+	EDataBookView *view;
+	GList         *l;
+
+	for (l = bf->priv->views; l; l = l->next) {
+		view = l->data;
+		e_data_book_view_enable_contact_status_notifications (view, id, enabled);
+	}
+}
+
+static gchar *
+collect_uri_for_field (EContact         *old_contact,
+		       EContact         *new_contact,
+		       EContactField     field)
+{
+	EContactPhoto *old_photo, *new_photo;
+
+	old_photo = e_contact_get (old_contact, field);
+	if (!old_photo)
+		return NULL;
+
+	if (new_contact) {
+
+		new_photo = e_contact_get (new_contact, field);
+
+		if (new_photo == NULL ||
+		    strcmp (old_photo->data.uri, new_photo->data.uri))
+			return g_strdup (old_photo->data.uri);
+	} else {
+		return g_strdup (old_photo->data.uri);
+	}
+
+	return NULL;
+}
+
+
+static void
+backend_maybe_push_delete_uri (EBookBackendFile *bf,
+			       const gchar      *id,
+			       EContact         *new_contact)
+{
+	DB                *db = bf->priv->file_db;
+	DBT                id_dbt, vcard_dbt;
+	ContactDeleteInfo *delete_info;
+	GList             *confirmations;
+	gchar             *uri_photo, *uri_logo, *vcard;
+	EContact          *old_contact;
+	gint               db_error;
+
+	/* Get the old contact from the db and compare the photo fields */
+	string_to_dbt (id, &id_dbt);
+	memset (&vcard_dbt, 0, sizeof (vcard_dbt));
+	vcard_dbt.flags = DB_DBT_MALLOC;
+		
+	db_error = db->get (db, NULL, &id_dbt, &vcard_dbt, 0);
+
+	if (db_error == 0) {
+		vcard = vcard_dbt.data;
+	} else {
+		g_warning (G_STRLOC ": db->get failed with %s", db_strerror (db_error));
+		return;
+	}
+
+	old_contact = create_contact (id, vcard);
+	g_free (vcard);
+
+	/* If there is no new contact, collect all the uris to delete from old_contact
+	 *
+	 * Otherwise, if any of the photo uri fields have changed in new_contact, then collect the
+	 * old uris for those fields from old_contact to delete
+	 */
+	uri_photo = collect_uri_for_field (old_contact, new_contact, E_CONTACT_PHOTO);
+	uri_logo  = collect_uri_for_field (old_contact, new_contact, E_CONTACT_LOGO);
+
+	if (!uri_photo && !uri_logo) {
+		g_object_unref (old_contact);
+		return;
+	}
+
+	id = e_contact_get_const (old_contact, E_CONTACT_UID);
+
+	if ((delete_info = 
+	     g_hash_table_lookup (bf->priv->delete_pool, id)) == NULL) {
+		delete_info = delete_info_new (id);
+
+		g_hash_table_insert (bf->priv->delete_pool, delete_info->id, delete_info);
+	}
+
+	if (uri_photo)
+		g_array_append_val (delete_info->uris, uri_photo);
+	if (uri_logo)
+		g_array_append_val (delete_info->uris, uri_logo);
+
+	backend_enable_contact_notifications (bf, id, TRUE);
+
+	/* Each view needs to confirm once for this contact, if there
+	 * are subsequent modifications then each view must confirm once
+	 * per notification sent */
+	confirmations = g_list_copy (bf->priv->views);
+	delete_info->expected_confirmations = 
+		g_list_concat (delete_info->expected_confirmations, confirmations);
+
+	g_object_unref (old_contact);
+}
+
+
+static void
+delete_info_pop_delete_url (EBookBackendFile  *bf,
+			    EDataBookView     *view,
+			    ContactDeleteInfo *delete_info)
+{
+	GList *node;
+
+	if ((node =
+	     g_list_find (delete_info->expected_confirmations, view)) != NULL) {
+		delete_info->expected_confirmations = 
+			g_list_delete_link (delete_info->expected_confirmations, node);
+
+		/* Disable confirmation notifications as soon as there are no more 
+		 * views expected to confirm for this contact
+		 */
+		if (!g_list_find (delete_info->expected_confirmations, view))
+			e_data_book_view_enable_contact_status_notifications (view, delete_info->id, FALSE);
+	}
+
+	/* This will cause the ContactDeleteInfo to be freed and the associated filesx unlinked */
+	if (!delete_info->expected_confirmations)
+		g_hash_table_remove (bf->priv->delete_pool, delete_info->id);
+}
+
+static void
+backend_contact_status_changed (EDataBookView             *view,
+				const gchar               *id,
+				EDataBookViewContactStatus status,
+				guint                      pending,
+				EBookBackendFile          *bf)
+{
+	ContactDeleteInfo *delete_info;
+
+	delete_info = g_hash_table_lookup (bf->priv->delete_pool, id);
+
+	if (!delete_info) {
+		g_warning ("Received status notification for a contact that we are not interested in");
+		return;
+	}
+
+	if (status == E_DATA_BOOK_VIEW_CONTACT_STATUS_EXISTS ||
+	    status == E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT) {
+		delete_info_pop_delete_url (bf, view, delete_info);
+	}
+}
+
+static void
+backend_add_view (EBookBackendFile *bf,
+		  EDataBookView    *view)
+{
+	bf->priv->views = g_list_prepend (bf->priv->views, view);
+	g_signal_connect (view, "contact-status-changed",
+			  G_CALLBACK (backend_contact_status_changed), bf);
+
+	g_message ("Added View");
+}
+
+typedef struct {
+	EDataBookView     *view;
+	EBookBackendFile  *bf;
+} ViewRemoveData;
+
+static void
+backend_remove_view_delete_pool_func (const gchar       *uid,
+				      ContactDeleteInfo *info,
+				      ViewRemoveData    *data)
+{
+	GList *node;
+
+	while ((node = g_list_find (info->expected_confirmations, data->view)) != NULL) {
+		delete_info_pop_delete_url (data->bf, data->view, info);
+	}
+}
+
+static void
+backend_remove_view (EBookBackendFile *bf,
+		     EDataBookView    *view)
+{
+	ViewRemoveData data = { view, bf };
+
+	bf->priv->views = g_list_remove (bf->priv->views, view);
+	g_signal_handlers_disconnect_by_func (view, backend_contact_status_changed, bf);
+
+	/* XXX We need to go through the delete_pool and unref any ContactDeleteEntrys
+	 * that we might be waiting on this view for */
+	g_hash_table_foreach (bf->priv->delete_pool, (GHFunc)backend_remove_view_delete_pool_func, &data);
+
+	g_message ("Removed View");
+}
+
 static gchar *
 e_book_backend_file_extract_path_from_source (ESource      *source, 
 					      GetPathType   path_type,
@@ -220,7 +532,6 @@ maybe_transform_vcard_field_for_photo (EBookBackendFile *bf,
 		/* XXX Create a good file extension based on mime type */
 		new_photo_path = safe_name_for_photo (bf, source, contact, field);
 		directory      = g_path_get_dirname (new_photo_path);
-		g_free (directory);
 
 		if ((uri = 
 		     g_filename_to_uri (new_photo_path, NULL, error)) == NULL) {
@@ -228,14 +539,12 @@ maybe_transform_vcard_field_for_photo (EBookBackendFile *bf,
 			status = STATUS_ERROR;
 		} else if (!create_directory (directory, error)) {
 
-			g_free (uri);
 			status = STATUS_ERROR;
 		} else if (!g_file_set_contents (new_photo_path,
 						 (const gchar *)photo->data.inlined.data,
 						 photo->data.inlined.length,
 						 error)) {
 
-			g_free (uri);
 			status = STATUS_ERROR;
 		} else {
 			g_message ("Transforming vcard photo to uri %s\n", uri);
@@ -249,7 +558,9 @@ maybe_transform_vcard_field_for_photo (EBookBackendFile *bf,
 			status = STATUS_MODIFIED;
 		}
 
+		g_free (uri);
 		g_free (new_photo_path);
+		g_free (directory);
 	}
 
 	return status;
@@ -292,47 +603,6 @@ maybe_transform_vcard_for_photo (EBookBackendFile *bf,
 }
 
 
-
-static void
-db_error_to_gerror (const gint db_error, GError **perror)
-{
-	if (db_error && perror && *perror)
-		g_clear_error (perror);
-
-	switch (db_error) {
-	case 0:
-		return;
-	case DB_NOTFOUND:
-		g_propagate_error (perror, EDB_ERROR (CONTACT_NOT_FOUND));
-		return;
-	case EACCES:
-		g_propagate_error (perror, EDB_ERROR (PERMISSION_DENIED));
-		return;
-	default:
-		g_propagate_error (perror, e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR, "db error 0x%x (%s)", db_error, db_strerror (db_error) ? db_strerror (db_error) : "Unknown error"));
-		return;
-	}
-}
-
-static void
-string_to_dbt(const gchar *str, DBT *dbt)
-{
-	memset (dbt, 0, sizeof (*dbt));
-	dbt->data = (gpointer)str;
-	dbt->size = strlen (str) + 1;
-	dbt->flags = DB_DBT_USERMEM;
-}
-
-static EContact*
-create_contact (gchar *uid, const gchar *vcard)
-{
-	EContact *contact = e_contact_new_from_vcard (vcard);
-	if (!e_contact_get_const (contact, E_CONTACT_UID))
-		e_contact_set (contact, E_CONTACT_UID, uid);
-
-	return contact;
-}
-
 static gboolean
 build_summary (EBookBackendFilePrivate *bfpriv)
 {
@@ -465,12 +735,11 @@ do_create(EBookBackendFile  *bf,
 		status = maybe_transform_vcard_for_photo (bf, *contact, &transformed_vcard, perror);
 
 		if (status == STATUS_MODIFIED && transformed_vcard) {
-			/* XXX
-			 *
-			 * Here we need to mark the newly entered contact as
-			 * modified and tell the sender that it's contact was
-			 * implicitly modified.
-			 *
+
+			/* Here we just dump the new version of the vcard into
+			 * the DB, the returned EContact is correct and stores
+			 * images by uri already so the following notifications
+			 * to the views should be correct.
 			 */
 			string_to_dbt (id,                &id_dbt);
 			string_to_dbt (transformed_vcard, &vcard_dbt);
@@ -486,7 +755,7 @@ do_create(EBookBackendFile  *bf,
 	if (perror && !perror)
 		db_error_to_gerror (db_error, perror);
 
-	return db_error == 0 && status == STATUS_NORMAL;
+	return db_error == 0;
 }
 
 static void
@@ -520,9 +789,15 @@ e_book_backend_file_remove_contacts (EBookBackendSync *backend,
 	GList          *l;
 	GList          *removed_cards = NULL;
 
+	g_message ("Remove contact begin\n");
+
 	for (l = id_list; l; l = l->next) {
 		id = l->data;
 
+		/* First collect uris to delete */
+		backend_maybe_push_delete_uri (bf, id, NULL);
+
+		/* Then go on to delete from the db */
 		string_to_dbt (id, &id_dbt);
 
 		db_error = db->del (db, NULL, &id_dbt, 0);
@@ -544,15 +819,13 @@ e_book_backend_file_remove_contacts (EBookBackendSync *backend,
 
 	*ids = removed_cards;
 
+	g_message ("Remove contact removing summaries\n");
+
 	for (l = removed_cards; l; l = l->next) {
 		gchar *id = l->data;
 		e_book_backend_summary_remove_contact (bf->priv->summary, id);
 	}
-
-	/* XXX Collect the uris that need to be removed and track the views
-	 * until they've received the remove notification before actually
-	 * deleting the photo fields.
-	 */
+	g_message ("Remove contact end\n");
 }
 
 static void
@@ -578,6 +851,9 @@ e_book_backend_file_modify_contact (EBookBackendSync *backend,
 		return;
 	}
 
+	/* Queue a delete on the photo file uris if needed */
+	backend_maybe_push_delete_uri (bf, id, NULL);
+
 	/* update the revision (modified time of contact) */
 	set_revision (*contact);
 	vcard_with_rev = e_vcard_to_string (E_VCARD (*contact), EVC_FORMAT_VCARD_30);
@@ -1057,6 +1333,8 @@ e_book_backend_file_start_book_view (EBookBackend  *backend,
 
 	e_flag_wait (closure->running);
 
+	backend_add_view (E_BOOK_BACKEND_FILE (backend), book_view);
+
 	/* at this point we know the book view thread is actually running */
 	d(printf ("returning from start_book_view\n"));
 }
@@ -1077,6 +1355,8 @@ e_book_backend_file_stop_book_view (EBookBackend  *backend,
 
 	if (need_join)
 		g_thread_join (closure->thread);
+
+	backend_remove_view (E_BOOK_BACKEND_FILE (backend), book_view);
 }
 
 typedef struct {
@@ -1720,14 +2000,8 @@ e_book_backend_file_remove (EBookBackendSync *backend,
 	EBookBackendFile *bf = E_BOOK_BACKEND_FILE (backend);
 	GDir *dir;
 
-	if (-1 == g_unlink (bf->priv->filename)) {
-		if (errno == EACCES || errno == EPERM) {
-			g_propagate_error (perror, EDB_ERROR (PERMISSION_DENIED));
-		} else {
-			g_propagate_error (perror, e_data_book_create_error_fmt (E_DATA_BOOK_STATUS_OTHER_ERROR, "Failed to remove file '%s': %s", bf->priv->filename, g_strerror (errno)));
-		}
+	if (!remove_file (bf->priv->filename, perror))
 		return;
-	}
 
 	/* unref the summary before we remove the file so it's not written out again */
 	g_object_unref (bf->priv->summary);
@@ -1832,6 +2106,11 @@ e_book_backend_file_dispose (GObject *object)
 		bf->priv->summary = NULL;
 	}
 
+	if (bf->priv->delete_pool) {
+		g_hash_table_destroy (bf->priv->delete_pool);
+		bf->priv->delete_pool = NULL;
+	}
+
 	G_OBJECT_CLASS (e_book_backend_file_parent_class)->dispose (object);
 }
 
@@ -1949,4 +2228,8 @@ e_book_backend_file_init (EBookBackendFile *backend)
 	priv             = g_new0 (EBookBackendFilePrivate, 1);
 
 	backend->priv = priv;
+
+	priv->delete_pool =
+		g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+				       (GDestroyNotify)delete_info_finish);
 }
diff --git a/addressbook/libebook/e-book-view.c b/addressbook/libebook/e-book-view.c
index 6da2d7e..6c7a98a 100644
--- a/addressbook/libebook/e-book-view.c
+++ b/addressbook/libebook/e-book-view.c
@@ -54,6 +54,19 @@ enum {
 static guint signals[LAST_SIGNAL];
 
 static void
+contacts_confirm_ready_cb (EGdbusBookView *object,
+			   GAsyncResult   *res,
+			   EBookView      *book_view)
+{
+	GError *error = NULL;
+
+	if (!e_gdbus_book_view_call_confirm_contacts_finish (object, res, &error)) {
+		g_warning ("Unable to confirm contact update with addressbook: %s\n", error->message);
+		g_error_free (error);
+	}
+}
+
+static void
 contacts_added_cb (EGdbusBookView *object, const gchar * const *vcards, EBookView *book_view)
 {
 	const gchar * const *p;
@@ -72,6 +85,11 @@ contacts_added_cb (EGdbusBookView *object, const gchar * const *vcards, EBookVie
 
 	g_list_foreach (contacts, (GFunc)g_object_unref, NULL);
 	g_list_free (contacts);
+
+	/* Notify the backend that we're finished with the changes */
+	e_gdbus_book_view_call_confirm_contacts (object, NULL, 
+						 (GAsyncReadyCallback)contacts_confirm_ready_cb,
+						 book_view);
 }
 
 static void
@@ -92,6 +110,11 @@ contacts_changed_cb (EGdbusBookView *object, const gchar * const *vcards, EBookV
 
 	g_list_foreach (contacts, (GFunc)g_object_unref, NULL);
 	g_list_free (contacts);
+
+	/* Notify the backend that we're finished with the changes */
+	e_gdbus_book_view_call_confirm_contacts (object, NULL, 
+						 (GAsyncReadyCallback)contacts_confirm_ready_cb,
+						 book_view);
 }
 
 static void
@@ -112,6 +135,11 @@ contacts_removed_cb (EGdbusBookView *object, const gchar * const *ids, EBookView
 
 	/* No need to free the values, our caller will */
 	g_list_free (list);
+
+	/* Notify the backend that we're finished with the changes */
+	e_gdbus_book_view_call_confirm_contacts (object, NULL, 
+						 (GAsyncReadyCallback)contacts_confirm_ready_cb,
+						 book_view);
 }
 
 static void
diff --git a/addressbook/libedata-book/Makefile.am b/addressbook/libedata-book/Makefile.am
index 997c727..df0b241 100644
--- a/addressbook/libedata-book/Makefile.am
+++ b/addressbook/libedata-book/Makefile.am
@@ -3,7 +3,11 @@ glib_enum_headers=e-data-book-types.h
 glib_enum_define=E_DATA_BOOK
 glib_enum_prefix=e_data_book
 
-ENUM_GENERATED = e-data-book-enumtypes.h e-data-book-enumtypes.c
+ENUM_GENERATED = \
+	e-data-book-enumtypes.h \
+	e-data-book-enumtypes.c \
+	e-data-book-marshal.h \
+	e-data-book-marshal.c
 
 # The library
 lib_LTLIBRARIES = libedata-book-1.2.la
diff --git a/addressbook/libedata-book/e-book-backend.h b/addressbook/libedata-book/e-book-backend.h
index 9cebf95..6ef1f5e 100644
--- a/addressbook/libedata-book/e-book-backend.h
+++ b/addressbook/libedata-book/e-book-backend.h
@@ -75,11 +75,14 @@ struct _EBookBackendClass {
 
 	void (*sync) (EBookBackend *backend);
 
+	void (* view_added)   (EBookBackend  *backend,
+			       EDataBookView *view);
+	void (* view_removed) (EBookBackend  *backend,
+			       EDataBookView *view);
+
 	/* Padding for future expansion */
 	void (*_pas_reserved1) (void);
 	void (*_pas_reserved2) (void);
-	void (*_pas_reserved3) (void);
-	void (*_pas_reserved4) (void);
 };
 
 const gchar *e_book_backend_get_cache_dir           (EBookBackend             *backend);
diff --git a/addressbook/libedata-book/e-data-book-types.h b/addressbook/libedata-book/e-data-book-types.h
index c6cdfaa..abdc4a6 100644
--- a/addressbook/libedata-book/e-data-book-types.h
+++ b/addressbook/libedata-book/e-data-book-types.h
@@ -87,6 +87,14 @@ typedef struct {
 	gchar *vcard;
 } EDataBookChange;
 
+typedef enum {
+	E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT = 0,
+	E_DATA_BOOK_VIEW_CONTACT_STATUS_EXISTS,
+	E_DATA_BOOK_VIEW_CONTACT_STATUS_ADDING,
+	E_DATA_BOOK_VIEW_CONTACT_STATUS_MODIFYING,
+	E_DATA_BOOK_VIEW_CONTACT_STATUS_REMOVING
+} EDataBookViewContactStatus;
+
 G_END_DECLS
 
 #endif /* __E_DATA_BOOK_TYPES_H__ */
diff --git a/addressbook/libedata-book/e-data-book-view.c b/addressbook/libedata-book/e-data-book-view.c
index a6df927..ec2ffe7 100644
--- a/addressbook/libedata-book/e-data-book-view.c
+++ b/addressbook/libedata-book/e-data-book-view.c
@@ -28,6 +28,8 @@
 #include <libebook/e-contact.h>
 #include <libebook/e-book-view.h>
 #include "e-data-book-view.h"
+#include "e-data-book-marshal.h"
+#include "e-data-book-enumtypes.h"
 
 #include "e-gdbus-egdbusbookview.h"
 
@@ -52,13 +54,17 @@ struct _EDataBookViewPrivate {
 	gint              max_results;
 	EBookViewFlags    flags;
 
-	gboolean running;
-	gboolean complete;
+	gboolean running;      /* We are running in between calls to start/stop */
+	gboolean complete;     /* We are complete after sending the initial notifications */
 	GMutex *pending_mutex;
 
-	GArray *adds;
-	GArray *changes;
-	GArray *removes;
+	GArray *adds;          /* Array of pending vCards to add */
+	GArray *changes;       /* Array of pending vCards to modify */
+	GArray *removes;       /* Array of pending vCards to remove */
+	GSList *adds_meta;     /* List of ContactEntry blobs corresponding with the 'adds' vCards */
+	GSList *changes_meta;  /* List of ContactEntry blobs corresponding with the 'changes' vCards */
+	GSList *removes_meta;  /* List of ContactEntry blobs corresponding with the 'removes' vCards */
+	GQueue *transactions;  /* A Queue of ContactTransaction blobs tracking currently active transactions */
 
 	GHashTable *ids;
 	guint idle_id;
@@ -68,6 +74,269 @@ struct _EDataBookViewPrivate {
 static void e_data_book_view_dispose (GObject *object);
 static void e_data_book_view_finalize (GObject *object);
 
+
+/* Signal IDs */
+enum {
+	CONTACT_STATUS_CHANGED,
+	LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL];
+
+/***************************************************************
+ *           Local Contact and Transaction Bookkeeping         *
+ ***************************************************************/
+ 
+typedef enum {
+	CONTACT_TRANSACTION_NONE = 0,
+	CONTACT_TRANSACTION_ADD,
+	CONTACT_TRANSACTION_MODIFY,
+	CONTACT_TRANSACTION_REMOVE
+} ContactTransactionType;
+
+typedef struct {
+	gchar *id;         /* Contact ID */
+
+	guint  pending;    /* Number of pending transactions on this contact */
+
+	guint  status : 3; /* EDataBookViewContactStatus */
+	guint  notify : 1; /* Whether notifications are enabled */
+} ContactEntry;
+
+typedef struct {
+	ContactTransactionType type;
+
+	GSList *contacts;
+} ContactTransaction;
+
+
+static ContactEntry *
+contact_entry_new (EDataBookView               *view,
+		   const gchar                 *id,
+		   EDataBookViewContactStatus   status)
+{
+	ContactEntry *entry;
+
+	g_assert (id && id[0]);
+
+	entry         = g_slice_new0 (ContactEntry);
+	entry->id     = g_strdup (id);
+	entry->status = status;
+
+	g_hash_table_insert (view->priv->ids, entry->id, entry);
+
+	return entry;
+}
+
+static inline ContactEntry *
+contact_entry_get (EDataBookView *book_view,
+		   const gchar   *id)
+{
+	return g_hash_table_lookup (book_view->priv->ids, id);
+}
+
+static void
+contact_entry_free (ContactEntry *entry)
+{
+	g_message ("Freeing a contact entry %s !\n", entry->id);
+
+	g_free (entry->id);
+	g_slice_free (ContactEntry, entry);
+}
+
+static ContactTransaction *
+contact_transaction_new (ContactTransactionType  type,
+			 GSList                 *contacts)
+{
+	ContactTransaction *transaction = g_slice_new0 (ContactTransaction);
+
+	transaction->type     = type;
+	transaction->contacts = contacts;
+
+	return transaction;
+}
+
+static void
+contact_transaction_free (ContactTransaction *transaction)
+{
+	g_slist_free (transaction->contacts);
+	g_slice_free (ContactTransaction, transaction);
+}
+
+static void
+book_view_finish_transaction (EDataBookView      *view,
+			      ContactTransaction *transaction)
+{
+	EDataBookViewPrivate *priv = view->priv;
+	GSList               *l;
+
+	for (l = transaction->contacts; l; l = l->next) {
+		ContactEntry *entry = l->data;
+
+		if (entry->notify)
+			g_signal_emit (view, signals[CONTACT_STATUS_CHANGED], 0,
+				       entry->id, entry->status, entry->pending);
+
+
+		g_message ("Transaction complete: contact '%s' status %d with pending transactions %d",
+			   entry->id, entry->status, entry->pending);
+
+		/* After notifying that a contact is removed with
+		 * the 'absent' status we can remove it from our
+		 * local table too */
+		if (entry->status == E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT) {
+
+			/* This could get bad fast ... */
+			if (entry->pending > 0)
+				g_critical ("Transactions out of sync: "
+					    "Contact %s removed with pending transactions",
+					    entry->id);
+
+
+			g_message ("Transaction complete: removing contact entry '%s' from local cache",
+				   entry->id);
+
+
+			g_hash_table_remove (priv->ids, entry->id);
+		}
+	}
+
+	contact_transaction_free (transaction);
+}
+
+static void
+book_view_queue_transaction (EDataBookView          *view,
+			     const gchar            *id,
+			     gchar                  *vcard,
+			     ContactTransactionType  type)
+{
+	EDataBookViewPrivate *priv = view->priv;
+	ContactEntry         *entry;
+	gchar                *dup_id;
+
+	if ((entry = contact_entry_get (view, id)) == NULL)
+		entry = contact_entry_new (view, id, E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT);
+
+	/* Increment the pending transactions on this entry */
+	entry->pending++;
+
+	switch (type) {
+	case CONTACT_TRANSACTION_ADD:
+		
+		g_array_append_val (priv->adds, vcard);
+
+		priv->adds_meta = g_slist_prepend (priv->adds_meta, entry);
+		entry->status   = E_DATA_BOOK_VIEW_CONTACT_STATUS_ADDING;
+		break;
+	case CONTACT_TRANSACTION_MODIFY:
+		g_array_append_val (priv->changes, vcard);
+
+		priv->changes_meta = g_slist_prepend (priv->changes_meta, entry);
+		entry->status      = E_DATA_BOOK_VIEW_CONTACT_STATUS_MODIFYING;
+		break;
+	case CONTACT_TRANSACTION_REMOVE:
+		dup_id = g_strdup (id);
+		g_array_append_val (priv->removes, dup_id);
+
+		priv->removes_meta = g_slist_prepend (priv->changes_meta, entry);
+		entry->status      = E_DATA_BOOK_VIEW_CONTACT_STATUS_REMOVING;
+		break;
+	case CONTACT_TRANSACTION_NONE:
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+
+	/* Notify status change if interest exists */
+	if (entry->notify)
+		g_signal_emit (view, signals[CONTACT_STATUS_CHANGED], 0,
+			       entry->id, entry->status, entry->pending);
+
+	g_message ("Queueing transaction: contact '%s' status %d with pending transactions %d",
+		   entry->id, entry->status, entry->pending);
+
+	ensure_pending_flush_timeout (view);
+}
+
+static void
+book_view_send_pending_transaction (EDataBookView          *view,
+				    ContactTransactionType  type)
+{
+	EDataBookViewPrivate *priv = view->priv;
+	ContactTransaction   *transaction;
+	GSList               *contacts = NULL;
+	GArray               *array = NULL;
+
+	switch (type) {
+	case CONTACT_TRANSACTION_ADD:
+		if (priv->adds->len > 0) {
+			array           = priv->adds;
+			contacts        = priv->adds_meta;
+			priv->adds_meta = NULL;
+		}
+		break;
+	case CONTACT_TRANSACTION_MODIFY:
+		if (priv->changes->len > 0) {
+			array              = priv->changes;
+			contacts           = priv->changes_meta;
+			priv->changes_meta = NULL;
+		}
+		break;
+	case CONTACT_TRANSACTION_REMOVE:
+		if (priv->removes->len > 0) {
+			array              = priv->removes;
+			contacts           = priv->removes_meta;
+			priv->removes_meta = NULL;
+		}
+		break;
+	case CONTACT_TRANSACTION_NONE:
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+
+	/* For every add/change/remove notification we send to the proxy we save a
+	 * 'transaction' blob with a shallow list pointing to the effected
+	 * ContactEntry entries in our local contact table. We rely on dbus here to properly
+	 * serialize the transactions and line them up with the responses.
+	 *
+	 * Note: It would be more reliable with using g_dbus_connection_send_message_with_reply()
+	 * instead of g_dbus_connection_emit_signal() for the contact notifications, currently we
+	 * have no way of knowing if the proxy failed to handle the change or if it simply failed
+	 * to send us the confirmation response for some reason.
+	 */
+	if (!contacts || !array)
+		return;
+
+	transaction = contact_transaction_new (type, contacts);
+	g_queue_push_tail (priv->transactions, transaction);
+
+	switch (type) {
+	case CONTACT_TRANSACTION_ADD:
+		e_gdbus_book_view_emit_contacts_added (view->priv->gdbus_object,
+						       (const gchar * const *) array->data);
+		break;
+	case CONTACT_TRANSACTION_MODIFY:
+		e_gdbus_book_view_emit_contacts_changed (view->priv->gdbus_object,
+							 (const gchar * const *) array->data);
+		break;
+	case CONTACT_TRANSACTION_REMOVE:
+		e_gdbus_book_view_emit_contacts_changed (view->priv->gdbus_object,
+							 (const gchar * const *) array->data);
+		break;
+	case CONTACT_TRANSACTION_NONE:
+	default:
+		g_assert_not_reached ();
+		break;
+	}
+
+	reset_array (array);
+}
+
+
+/***************************************************************
+ *                         EDataBookView                       *
+ ***************************************************************/
 static void
 e_data_book_view_class_init (EDataBookViewClass *klass)
 {
@@ -77,6 +346,18 @@ e_data_book_view_class_init (EDataBookViewClass *klass)
 
 	object_class->dispose = e_data_book_view_dispose;
 	object_class->finalize = e_data_book_view_finalize;
+
+	signals[CONTACT_STATUS_CHANGED] =
+		g_signal_new ("contact-status-changed",
+			      G_TYPE_FROM_CLASS (klass),
+			      G_SIGNAL_RUN_LAST,
+			      G_STRUCT_OFFSET (EDataBookViewClass, contact_status_changed),
+			      NULL, NULL,
+			      e_data_book_marshal_VOID__STRING_ENUM_UINT,
+			      G_TYPE_NONE, 3, 
+			      G_TYPE_STRING, 
+			      E_TYPE_DATA_BOOK_VIEW_CONTACT_STATUS,
+			      G_TYPE_UINT);
 }
 
 /**
@@ -113,42 +394,6 @@ book_destroyed_cb (gpointer data, GObject *dead)
 	}
 }
 
-static void
-send_pending_adds (EDataBookView *view)
-{
-	EDataBookViewPrivate *priv = view->priv;
-
-	if (priv->adds->len == 0)
-		return;
-
-	e_gdbus_book_view_emit_contacts_added (view->priv->gdbus_object, (const gchar * const *) priv->adds->data);
-	reset_array (priv->adds);
-}
-
-static void
-send_pending_changes (EDataBookView *view)
-{
-	EDataBookViewPrivate *priv = view->priv;
-
-	if (priv->changes->len == 0)
-		return;
-
-	e_gdbus_book_view_emit_contacts_changed (view->priv->gdbus_object, (const gchar * const *) priv->changes->data);
-	reset_array (priv->changes);
-}
-
-static void
-send_pending_removes (EDataBookView *view)
-{
-	EDataBookViewPrivate *priv = view->priv;
-
-	if (priv->removes->len == 0)
-		return;
-
-	e_gdbus_book_view_emit_contacts_removed (view->priv->gdbus_object, (const gchar * const *) priv->removes->data);
-	reset_array (priv->removes);
-}
-
 static gboolean
 pending_flush_timeout_cb (gpointer data)
 {
@@ -159,9 +404,9 @@ pending_flush_timeout_cb (gpointer data)
 
 	priv->flush_id = 0;
 
-	send_pending_adds (view);
-	send_pending_changes (view);
-	send_pending_removes (view);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_ADD);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_MODIFY);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_REMOVE);
 
 	g_mutex_unlock (priv->pending_mutex);
 
@@ -184,40 +429,36 @@ ensure_pending_flush_timeout (EDataBookView *view)
  * @vcard.
  */
 static void
-notify_change (EDataBookView *view, gchar *vcard)
+notify_change (EDataBookView *view, const gchar *id, gchar *vcard)
 {
 	EDataBookViewPrivate *priv = view->priv;
-	send_pending_adds (view);
-	send_pending_removes (view);
+
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_ADD);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_REMOVE);
 
 	if (priv->changes->len == THRESHOLD_ITEMS) {
-		send_pending_changes (view);
+		book_view_send_pending_transaction (view, CONTACT_TRANSACTION_MODIFY);
 	}
 
-	g_array_append_val (priv->changes, vcard);
-
-	ensure_pending_flush_timeout (view);
+	book_view_queue_transaction (view, id, vcard, CONTACT_TRANSACTION_MODIFY);
 }
 
 /*
  * Queue @id to be sent as a change notification. This takes ownership of @id.
  */
 static void
-notify_remove (EDataBookView *view, gchar *id)
+notify_remove (EDataBookView *view, const gchar *id)
 {
 	EDataBookViewPrivate *priv = view->priv;
 
-	send_pending_adds (view);
-	send_pending_changes (view);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_ADD);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_MODIFY);
 
 	if (priv->removes->len == THRESHOLD_ITEMS) {
-		send_pending_removes (view);
+		book_view_send_pending_transaction (view, CONTACT_TRANSACTION_REMOVE);
 	}
 
-	g_array_append_val (priv->removes, id);
-	g_hash_table_remove (priv->ids, id);
-
-	ensure_pending_flush_timeout (view);
+	book_view_queue_transaction (view, id, NULL, CONTACT_TRANSACTION_REMOVE);
 }
 
 /*
@@ -227,25 +468,23 @@ notify_remove (EDataBookView *view, gchar *id)
 static void
 notify_add (EDataBookView *view, const gchar *id, gchar *vcard)
 {
-	EBookViewFlags flags;
+	EBookViewFlags        flags;
 	EDataBookViewPrivate *priv = view->priv;
-	send_pending_changes (view);
-	send_pending_removes (view);
+
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_MODIFY);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_REMOVE);
 
 	/* Do not send contact add notifications during initial stage */
 	flags = e_data_book_view_get_flags (view);
 	if (priv->complete || (flags & E_BOOK_VIEW_NOTIFY_INITIAL) != 0) {
-		if (priv->adds->len == THRESHOLD_ITEMS) {
-			send_pending_adds (view);
+		if (priv->adds->len == THRESHOLD_ITEMS) {	
+			book_view_send_pending_transaction (view, CONTACT_TRANSACTION_MODIFY);
 		}
 
-		g_array_append_val (priv->adds, vcard);
-
-		ensure_pending_flush_timeout (view);
+		book_view_queue_transaction (view, id, vcard, CONTACT_TRANSACTION_ADD);
 	}
-
-	g_hash_table_insert (priv->ids, g_strdup (id),
-			     GUINT_TO_POINTER (1));
+	else
+		contact_entry_new (view, id, E_DATA_BOOK_VIEW_CONTACT_STATUS_EXISTS);
 }
 
 static void
@@ -265,6 +504,58 @@ reset_array (GArray *array)
 }
 
 /**
+ * e_data_book_view_get_contact_status:
+ * @book_view
+ * @id: the UID of the to check the status of
+ *
+ * Gets the internally tracked #EDataBookViewContactStatus
+ * of a said #EContact referred to by @id.
+ *
+ * Returns: The status of the contact referred to by @id.
+ */
+EDataBookViewContactStatus 
+e_data_book_view_get_contact_status (EDataBookView *book_view,
+				     const gchar   *id)
+{
+	ContactEntry *entry;
+
+	entry =	contact_entry_get (book_view, id);
+
+	if (!entry)
+		return E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT;
+
+	return entry->status;
+}
+
+/**
+ * e_data_book_view_enable_contact_status_notifications:
+ * @book_view: an #EDataBookView
+ * @id: the UID of the contact to track/untrack
+ * @enabled: whether to enable/disable notifications
+ *
+ * If notifications are enabled for the said #EContact referred
+ * to by @id then status notifications for that contact will
+ * be made in response to add/remove/change notifications sent
+ * to the client.
+ *
+ * This makes it possible to safely track when the view has been
+ * notified for a said modification to a contact.
+ */
+void
+e_data_book_view_enable_contact_status_notifications (EDataBookView *book_view,
+						      const gchar   *id,
+						      gboolean       enabled)
+{
+	ContactEntry *entry;
+
+	entry =	contact_entry_get (book_view, id);
+
+	if (entry)
+		entry->notify = enabled;
+}
+
+
+/**
  * e_data_book_view_notify_update:
  * @book_view: an #EDataBookView
  * @contact: an #EContact
@@ -280,6 +571,7 @@ e_data_book_view_notify_update (EDataBookView *book_view,
                                 EContact      *contact)
 {
 	EDataBookViewPrivate *priv = book_view->priv;
+	EDataBookViewContactStatus status;
 	gboolean currently_in_view, want_in_view;
 	const gchar *id;
 	gchar *vcard;
@@ -290,9 +582,12 @@ e_data_book_view_notify_update (EDataBookView *book_view,
 	g_mutex_lock (priv->pending_mutex);
 
 	id = e_contact_get_const (contact, E_CONTACT_UID);
+	status = e_data_book_view_get_contact_status (book_view, id);
 
-	currently_in_view =
-		g_hash_table_lookup (priv->ids, id) != NULL;
+	currently_in_view = 
+		(status != E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT) &&
+		(status != E_DATA_BOOK_VIEW_CONTACT_STATUS_REMOVING);
+		
 	want_in_view =
 		e_book_backend_sexp_match_contact (priv->card_sexp, contact);
 
@@ -301,12 +596,12 @@ e_data_book_view_notify_update (EDataBookView *book_view,
 					   EVC_FORMAT_VCARD_30);
 
 		if (currently_in_view)
-			notify_change (book_view, vcard);
+			notify_change (book_view, id, vcard);
 		else
 			notify_add (book_view, id, vcard);
 	} else {
 		if (currently_in_view)
-			notify_remove (book_view, g_strdup (id));
+			notify_remove (book_view, id);
 		/* else nothing; we're removing a card that wasn't there */
 	}
 
@@ -330,6 +625,7 @@ void
 e_data_book_view_notify_update_vcard (EDataBookView *book_view, gchar *vcard)
 {
 	EDataBookViewPrivate *priv = book_view->priv;
+	EDataBookViewContactStatus status;
 	gboolean currently_in_view, want_in_view;
 	const gchar *id;
 	EContact *contact;
@@ -341,19 +637,22 @@ e_data_book_view_notify_update_vcard (EDataBookView *book_view, gchar *vcard)
 
 	contact = e_contact_new_from_vcard (vcard);
 	id = e_contact_get_const (contact, E_CONTACT_UID);
-	currently_in_view =
-		g_hash_table_lookup (priv->ids, id) != NULL;
+
+	status = e_data_book_view_get_contact_status (book_view, id);
+	currently_in_view = 
+		(status != E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT) &&
+		(status != E_DATA_BOOK_VIEW_CONTACT_STATUS_REMOVING);
 	want_in_view =
 		e_book_backend_sexp_match_contact (priv->card_sexp, contact);
 
 	if (want_in_view) {
 		if (currently_in_view)
-			notify_change (book_view, vcard);
+			notify_change (book_view, id, vcard);
 		else
 			notify_add (book_view, id, vcard);
 	} else {
 		if (currently_in_view)
-			notify_remove (book_view, g_strdup (id));
+			notify_remove (book_view, id);
 		else
 			/* else nothing; we're removing a card that wasn't there */
 			g_free (vcard);
@@ -388,6 +687,7 @@ void
 e_data_book_view_notify_update_prefiltered_vcard (EDataBookView *book_view, const gchar *id, gchar *vcard)
 {
 	EDataBookViewPrivate *priv = book_view->priv;
+	EDataBookViewContactStatus status;
 	gboolean currently_in_view;
 
 	if (!priv->running)
@@ -395,11 +695,13 @@ e_data_book_view_notify_update_prefiltered_vcard (EDataBookView *book_view, cons
 
 	g_mutex_lock (priv->pending_mutex);
 
-	currently_in_view =
-		g_hash_table_lookup (priv->ids, id) != NULL;
+	status = e_data_book_view_get_contact_status (book_view, id);
+	currently_in_view = 
+		(status != E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT) &&
+		(status != E_DATA_BOOK_VIEW_CONTACT_STATUS_REMOVING);
 
 	if (currently_in_view)
-		notify_change (book_view, vcard);
+		notify_change (book_view, id, vcard);
 	else
 		notify_add (book_view, id, vcard);
 
@@ -424,8 +726,8 @@ e_data_book_view_notify_remove (EDataBookView *book_view, const gchar *id)
 
 	g_mutex_lock (priv->pending_mutex);
 
-	if (g_hash_table_lookup (priv->ids, id))
-		notify_remove (book_view, g_strdup (id));
+	if (contact_entry_get (book_view, id))
+		notify_remove (book_view, id);
 
 	g_mutex_unlock (priv->pending_mutex);
 }
@@ -440,9 +742,9 @@ e_data_book_view_notify_remove (EDataBookView *book_view, const gchar *id)
  * in sync with the backend's.
  **/
 void
-e_data_book_view_notify_complete (EDataBookView *book_view, const GError *error)
+e_data_book_view_notify_complete (EDataBookView *view, const GError *error)
 {
-	EDataBookViewPrivate *priv = book_view->priv;
+	EDataBookViewPrivate *priv = view->priv;
 
 	if (!priv->running)
 		return;
@@ -451,9 +753,9 @@ e_data_book_view_notify_complete (EDataBookView *book_view, const GError *error)
 
 	g_mutex_lock (priv->pending_mutex);
 
-	send_pending_adds (book_view);
-	send_pending_changes (book_view);
-	send_pending_removes (book_view);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_ADD);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_MODIFY);
+	book_view_send_pending_transaction (view, CONTACT_TRANSACTION_REMOVE);
 
 	g_mutex_unlock (priv->pending_mutex);
 
@@ -593,6 +895,55 @@ impl_DataBookView_dispose (EGdbusBookView *object, GDBusMethodInvocation *invoca
 	return TRUE;
 }
 
+static gboolean
+impl_DataBookView_confirmContacts (EGdbusBookView *object, GDBusMethodInvocation *invocation, EDataBookView *book_view)
+{
+	ContactTransaction   *transaction;
+	ContactEntry         *entry;
+	GSList               *l;
+	
+	e_gdbus_book_view_complete_confirm_contacts (object, invocation);
+
+	/* Get the corresponding transaction from the queue and assume
+	 * the transactions are all received in the order they were sent.
+	 */
+	transaction = g_queue_pop_head (book_view->priv->transactions);
+	if (!transaction) {
+		g_critical ("Transactions out of sync !\n");
+		return TRUE;
+	}
+
+	for (l = transaction->contacts; l; l = l->next) {
+		entry = l->data;
+
+		switch (transaction->type) {
+		case CONTACT_TRANSACTION_ADD:
+		case CONTACT_TRANSACTION_MODIFY:
+			entry->status = E_DATA_BOOK_VIEW_CONTACT_STATUS_EXISTS;
+			break;
+		case CONTACT_TRANSACTION_REMOVE:
+			entry->status = E_DATA_BOOK_VIEW_CONTACT_STATUS_ABSENT;
+			break;
+		case CONTACT_TRANSACTION_NONE:
+		default:
+			g_assert_not_reached ();
+			break;
+		}
+
+		/* Decrement the pending transactions on this entry */
+		if (!entry->pending)
+			g_critical ("Transaction out of sync for contact %s", entry->id);
+		else
+			entry->pending--;
+	}
+
+	book_view_finish_transaction (book_view, transaction);
+
+	/* Here we need to remove *after* sending the notifications :-/ */
+
+	return TRUE;
+}
+
 static void
 e_data_book_view_init (EDataBookView *book_view)
 {
@@ -606,6 +957,7 @@ e_data_book_view_init (EDataBookView *book_view)
 	g_signal_connect (priv->gdbus_object, "handle-stop", G_CALLBACK (impl_DataBookView_stop), book_view);
 	g_signal_connect (priv->gdbus_object, "handle-set-flags", G_CALLBACK (impl_DataBookView_setFlags), book_view);
 	g_signal_connect (priv->gdbus_object, "handle-dispose", G_CALLBACK (impl_DataBookView_dispose), book_view);
+	g_signal_connect (priv->gdbus_object, "handle-confirm-contacts", G_CALLBACK (impl_DataBookView_confirmContacts), book_view);
 
 	priv->running = FALSE;
 	priv->complete = FALSE;
@@ -614,8 +966,11 @@ e_data_book_view_init (EDataBookView *book_view)
 	priv->adds = g_array_sized_new (TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS);
 	priv->changes = g_array_sized_new (TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS);
 	priv->removes = g_array_sized_new (TRUE, TRUE, sizeof (gchar *), THRESHOLD_ITEMS);
+	priv->transactions = g_queue_new ();
+
 
-	priv->ids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	priv->ids = g_hash_table_new_full (g_str_hash, g_str_equal, NULL,
+					   (GDestroyNotify)contact_entry_free);
 
 	priv->flush_id = 0;
 }
@@ -665,6 +1020,7 @@ e_data_book_view_finalize (GObject *object)
 {
 	EDataBookView *book_view = E_DATA_BOOK_VIEW (object);
 	EDataBookViewPrivate *priv = book_view->priv;
+	ContactTransaction   *transaction;
 
 	reset_array (priv->adds);
 	reset_array (priv->changes);
@@ -672,6 +1028,14 @@ e_data_book_view_finalize (GObject *object)
 	g_array_free (priv->adds, TRUE);
 	g_array_free (priv->changes, TRUE);
 	g_array_free (priv->removes, TRUE);
+	g_slist_free (priv->adds_meta);
+	g_slist_free (priv->changes_meta);
+	g_slist_free (priv->removes_meta);
+
+	while ((transaction = 
+		g_queue_pop_head (priv->transactions)) != NULL)
+		contact_transaction_free (transaction);
+	g_queue_free (priv->transactions);
 
 	g_free (priv->card_query);
 	g_strfreev (priv->requested_fields);
diff --git a/addressbook/libedata-book/e-data-book-view.h b/addressbook/libedata-book/e-data-book-view.h
index 954468a..5b59bab 100644
--- a/addressbook/libedata-book/e-data-book-view.h
+++ b/addressbook/libedata-book/e-data-book-view.h
@@ -51,6 +51,11 @@ struct _EDataBookView {
 
 struct _EDataBookViewClass {
 	GObjectClass parent;
+
+	void (* contact_status_changed) (EDataBook                  *book,
+					 const gchar                *id,
+					 EDataBookViewContactStatus  status,
+					 guint                       pending_transactions);
 };
 
 EDataBookView *e_data_book_view_new                  (EDataBook           *book,
@@ -90,6 +95,14 @@ void         e_data_book_view_unref                  (EDataBookView
 
 GType        e_data_book_view_get_type               (void);
 
+
+EDataBookViewContactStatus e_data_book_view_get_contact_status (EDataBookView *book_view,
+								const gchar   *id);
+
+void         e_data_book_view_enable_contact_status_notifications (EDataBookView *book_view,
+								   const gchar   *id,
+								   gboolean       enabled);
+
 G_END_DECLS
 
 #endif /* __E_DATA_BOOK_VIEW_H__ */
diff --git a/addressbook/libegdbus/e-gdbus-egdbusbookview.c b/addressbook/libegdbus/e-gdbus-egdbusbookview.c
index c44f8e4..3a858d9 100644
--- a/addressbook/libegdbus/e-gdbus-egdbusbookview.c
+++ b/addressbook/libegdbus/e-gdbus-egdbusbookview.c
@@ -72,7 +72,8 @@ enum
   __START_METHOD,
   __STOP_METHOD,
   __SET_FLAGS_METHOD,
-  __DISPOSE_METHOD,
+  __DISPOSE_METHOD, 
+  __CONFIRM_CONTACTS_METHOD,
   __LAST_SIGNAL
 };
 
@@ -346,6 +347,7 @@ e_gdbus_book_view_default_init (EGdbusBookViewIface *iface)
   g_hash_table_insert (_method_name_to_id, (gpointer) "stop", GUINT_TO_POINTER (__STOP_METHOD));
   g_hash_table_insert (_method_name_to_id, (gpointer) "setFlags", GUINT_TO_POINTER (__SET_FLAGS_METHOD));
   g_hash_table_insert (_method_name_to_id, (gpointer) "dispose", GUINT_TO_POINTER (__DISPOSE_METHOD));
+  g_hash_table_insert (_method_name_to_id, (gpointer) "confirmContacts", GUINT_TO_POINTER (__CONFIRM_CONTACTS_METHOD));
   g_hash_table_insert (_signal_name_to_id, (gpointer) "ContactsAdded", GUINT_TO_POINTER (__CONTACTS_ADDED_SIGNAL));
   g_hash_table_insert (_signal_name_to_id, (gpointer) "ContactsChanged", GUINT_TO_POINTER (__CONTACTS_CHANGED_SIGNAL));
   g_hash_table_insert (_signal_name_to_id, (gpointer) "ContactsRemoved", GUINT_TO_POINTER (__CONTACTS_REMOVED_SIGNAL));
@@ -608,6 +610,31 @@ e_gdbus_book_view_default_init (EGdbusBookViewIface *iface)
                   1,
                   G_TYPE_DBUS_METHOD_INVOCATION);
 
+  /**
+   * EGdbusBookView::handle-confirm-contacts:
+   * @object: The exported object emitting the signal.
+   * @invocation: A #GDBusMethodInvocation object that can be used to return a value or error.
+   *
+   * On exported objects, this signal is emitted when a remote process (identified by @invocation) invokes the <literal>confirmContacts</literal> D-Bus method on @object. Use e_gdbus_book_view_complete_confirm_contacts() to return a value or g_dbus_method_invocation_return_error() to return an error.
+   *
+   * The signal is emitted in the thread-default main loop of the thread that e_gdbus_book_view_register_object() was called from.
+   *
+   * On proxies, this signal is never emitted.
+   *
+   * Returns: %TRUE if you want to handle the method call (will stop further handlers from being called), %FALSE otherwise.
+   */
+  signals[__CONFIRM_CONTACTS_METHOD] =
+    g_signal_new ("handle-confirm-contacts",
+                  G_TYPE_FROM_INTERFACE (iface),
+                  G_SIGNAL_RUN_LAST,
+                  G_STRUCT_OFFSET (EGdbusBookViewIface, handle_dispose),
+                  g_signal_accumulator_true_handled,
+                  NULL,
+                  _e_gdbus_gdbus_cclosure_marshaller_BOOLEAN__OBJECT,
+                  G_TYPE_BOOLEAN,
+                  1,
+                  G_TYPE_DBUS_METHOD_INVOCATION);
+
   /* GObject property definitions for D-Bus properties: */
 }
 
@@ -1016,6 +1043,67 @@ _out:
   return _ret;
 }
 
+
+/**
+ * e_gdbus_book_view_call_confirm_contacts:
+ * @proxy: A #EGdbusBookView.
+ * @cancellable: A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or %NULL if you don't care about the result of the method invocation.
+ * @user_data: Data to pass to @callback.
+ *
+ * Invokes the <literal>org.gnome.evolution.dataserver.addressbook.BookView.confirmContacts</literal>
+ * D-Bus method on the remote object represented by @proxy.
+ *
+ * This is an asynchronous method. When the operation is finished,
+ * callback will be invoked in the thread-default main loop of the
+ * thread you are calling this method from. You can then call
+ * e_gdbus_book_view_call_confirm_contacts_finish() to get the result of the operation.
+ */
+void e_gdbus_book_view_call_confirm_contacts (
+        EGdbusBookView *proxy,
+        GCancellable *cancellable,
+        GAsyncReadyCallback callback,
+        gpointer user_data)
+{
+  GVariant *_params;
+  _params = NULL;
+  g_dbus_proxy_call (G_DBUS_PROXY (proxy),
+                     "confirmContacts",
+                     _params,
+                     G_DBUS_CALL_FLAGS_NONE,
+                     -1,
+                     cancellable,
+                     callback,
+                     user_data);
+}
+
+/**
+ * e_gdbus_book_view_call_confirm_contacts_finish:
+ * @proxy: A #EGdbusBookView.
+ * @res: A #GAsyncResult obtained from the #GAsyncReadyCallback passed to e_gdbus_book_view_call_confirm_contacts().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes invoking the <literal>org.gnome.evolution.dataserver.addressbook.BookView.confirmContacts</literal>
+ * D-Bus method on the remote object represented by @proxy.
+ *
+ * Returns: %TRUE if the call succeeded, %FALSE if @error is set.
+ */
+gboolean e_gdbus_book_view_call_confirm_contacts_finish (
+        EGdbusBookView *proxy,
+        GAsyncResult *res,
+        GError **error)
+{
+  gboolean _ret = FALSE;
+  GVariant *_result;
+  _result = g_dbus_proxy_call_finish (G_DBUS_PROXY (proxy), res, error);
+  if (_result == NULL)
+    goto _out;
+  g_variant_unref (_result);
+  _ret = TRUE;
+_out:
+  return _ret;
+}
+
 /**
  * e_gdbus_book_view_complete_start:
  * @object: A #EGdbusBookView.
@@ -1097,6 +1185,26 @@ void e_gdbus_book_view_complete_dispose (
 }
 
 /**
+ * e_gdbus_book_view_complete_confirm_contacts:
+ * @object: A #EGdbusBookView.
+ * @invocation: A #GDBusMethodInvocation.
+ *
+ * Completes handling the <literal>org.gnome.evolution.dataserver.addressbook.BookView.confirmContacts</literal>
+ * D-Bus method invocation by returning a value.
+ *
+ * If you want to return an error, use g_dbus_method_invocation_return_error()
+ * or similar instead.
+ *
+ * This method will free @invocation, you cannot use it afterwards.
+ */
+void e_gdbus_book_view_complete_confirm_contacts (
+        EGdbusBookView *object,
+        GDBusMethodInvocation *invocation)
+{
+  g_dbus_method_invocation_return_value (invocation, NULL);
+}
+
+/**
  * e_gdbus_book_view_emit_contacts_added:
  * @object: A #EGdbusBookView.
  * @arg_vcards: Signal parameter.
@@ -1344,12 +1452,22 @@ static const GDBusMethodInfo e_gdbus_book_view_method_dispose =
   (GDBusAnnotationInfo **) NULL,
 };
 
+static const GDBusMethodInfo e_gdbus_book_view_method_confirmContacts =
+{
+  -1,
+  (gchar *) "confirmContacts",
+  (GDBusArgInfo **) NULL,
+  (GDBusArgInfo **) NULL,
+  (GDBusAnnotationInfo **) NULL,
+};
+
 static const GDBusMethodInfo * const e_gdbus_book_view_method_info_pointers[] =
 {
   &e_gdbus_book_view_method_start,
   &e_gdbus_book_view_method_stop,
   &e_gdbus_book_view_method_setFlags,
   &e_gdbus_book_view_method_dispose,
+  &e_gdbus_book_view_method_confirmContacts,
   NULL
 };
 
@@ -1377,18 +1495,9 @@ handle_method_call (GDBusConnection       *connection,
   switch (method_id)
     {
     case __START_METHOD:
-      {
-        EGdbusBookView *object = E_GDBUS_BOOK_VIEW (user_data);
-        gboolean handled;
-        g_signal_emit (object,
-                       signals[method_id],
-                       0, invocation, &handled);
-        if (!handled)
-          goto not_implemented;
-      }
-      break;
-
     case __STOP_METHOD:
+    case __DISPOSE_METHOD:
+    case __CONFIRM_CONTACTS_METHOD:
       {
         EGdbusBookView *object = E_GDBUS_BOOK_VIEW (user_data);
         gboolean handled;
@@ -1415,18 +1524,6 @@ handle_method_call (GDBusConnection       *connection,
       }
       break;
 
-    case __DISPOSE_METHOD:
-      {
-        EGdbusBookView *object = E_GDBUS_BOOK_VIEW (user_data);
-        gboolean handled;
-        g_signal_emit (object,
-                       signals[method_id],
-                       0, invocation, &handled);
-        if (!handled)
-          goto not_implemented;
-      }
-      break;
-
     default:
 not_implemented:
       g_dbus_method_invocation_return_error (invocation,
diff --git a/addressbook/libegdbus/e-gdbus-egdbusbookview.h b/addressbook/libegdbus/e-gdbus-egdbusbookview.h
index 2babb53..73e1f6e 100644
--- a/addressbook/libegdbus/e-gdbus-egdbusbookview.h
+++ b/addressbook/libegdbus/e-gdbus-egdbusbookview.h
@@ -37,6 +37,7 @@ typedef struct _EGdbusBookView EGdbusBookView; /* Dummy typedef */
  * @handle_stop: Handler for the #EGdbusBookView::handle-stop signal.
  * @handle_set_flags: Handler for the #EGdbusBookView::handle-set-flags signal.
  * @handle_dispose: Handler for the #EGdbusBookView::handle-dispose signal.
+ * @handle_confirm_contacts: Handler for the #EGdbusBookView::handle-confirm-contacts signal.
  *
  * Virtual table.
  */
@@ -197,6 +198,9 @@ struct _EGdbusBookViewIface
   gboolean (*handle_dispose) (
         EGdbusBookView *object,
         GDBusMethodInvocation *invocation);
+  gboolean (*handle_confirm_contacts) (
+        EGdbusBookView *object,
+        GDBusMethodInvocation *invocation);
 };
 
 /* C Bindings for properties */
@@ -268,6 +272,17 @@ gboolean e_gdbus_book_view_call_dispose_sync (
         GCancellable *cancellable,
         GError **error);
 
+void e_gdbus_book_view_call_confirm_contacts (
+        EGdbusBookView *proxy,
+        GCancellable *cancellable,
+        GAsyncReadyCallback callback,
+        gpointer user_data);
+
+gboolean e_gdbus_book_view_call_confirm_contacts_finish (
+        EGdbusBookView *proxy,
+        GAsyncResult *res,
+        GError **error);
+
 /* D-Bus Methods Completion Helpers */
 void e_gdbus_book_view_complete_start (
         EGdbusBookView *object,
@@ -285,6 +300,10 @@ void e_gdbus_book_view_complete_dispose (
         EGdbusBookView *object,
         GDBusMethodInvocation *invocation);
 
+void e_gdbus_book_view_complete_confirm_contacts (
+        EGdbusBookView *object,
+        GDBusMethodInvocation *invocation);
+
 /* D-Bus Signal Emission Helpers */
 void e_gdbus_book_view_emit_contacts_added (
         EGdbusBookView *object,
diff --git a/addressbook/tests/ebook/test-ebook-photo-is-uri.c b/addressbook/tests/ebook/test-ebook-photo-is-uri.c
index dde4e1a..37a258a 100644
--- a/addressbook/tests/ebook/test-ebook-photo-is-uri.c
+++ b/addressbook/tests/ebook/test-ebook-photo-is-uri.c
@@ -27,7 +27,8 @@ TleNB84DnjkD0P9VlxT4Nqck9pmn8JuFp2zo0cgCWFi2e7555/NSHXLadso2m3sU0NxlV65HM+\
 VdTW3rgwvsUpSvAFKUoAUxSlAClKUAKUpQB//2Q==";
 
 
-static GMainLoop *loop;
+static GMainLoop *loop = NULL;
+static gchar     *global_uid = NULL;
 
 static void
 print_contact (EContact *contact)
@@ -59,12 +60,28 @@ contacts_removed (EBookView *book_view, const GList *ids)
 	}
 }
 
-static void
-view_complete (EBookView *book_view, EBookViewStatus status, const gchar *error_msg)
+static gboolean
+quit_timeout (EBookView *book_view)
 {
+
+
 	e_book_view_stop (book_view);
 	g_object_unref (book_view);
 	g_main_loop_quit (loop);
+
+	return FALSE;
+}
+
+static void
+view_complete (EBookView *book_view, EBookViewStatus status, const gchar *error_msg)
+{
+	EBook *book = e_book_view_get_book (book_view);
+	GError *error = NULL;
+
+	if (!e_book_remove_contact (book, global_uid, &error))
+		g_error ("Error removing contact: %s", error->message);
+
+	g_timeout_add (500, (GSourceFunc)quit_timeout, book_view);
 }
 
 static void
@@ -105,6 +122,8 @@ add_contact (EBook *book)
 
 	/* verify the contact was added "successfully" (not thorough) */
 	g_assert (ebook_test_utils_contacts_are_equal_shallow (contact, final));
+
+	global_uid = g_strdup (uid);
 }
 
 static void



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