[evolution-data-server/openismus-work] Work in progress: Implementing policy to store photos as uris
- From: Tristan Van Berkom <tvb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/openismus-work] Work in progress: Implementing policy to store photos as uris
- Date: Sat, 2 Jul 2011 22:36:41 +0000 (UTC)
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]