[evolution-mapi] Add "Listen for server notifications" option to mail/book/calendar



commit 85b81798cc422d9bd311a0ad872f14dc6fb7df98
Author: Milan Crha <mcrha redhat com>
Date:   Wed Nov 30 18:27:38 2011 +0100

    Add "Listen for server notifications" option to mail/book/calendar

 src/account-setup-eplugin/e-mapi-account-setup.c |    3 +
 src/addressbook/e-book-backend-mapi-contacts.c   |  106 ++++++
 src/addressbook/e-book-backend-mapi.c            |  133 +++++---
 src/addressbook/e-book-backend-mapi.h            |    1 +
 src/calendar/e-cal-backend-mapi.c                |  103 +++++-
 src/camel/Makefile.am                            |    5 +-
 src/camel/camel-mapi-folder.c                    |    9 +-
 src/camel/camel-mapi-notifications.c             |  271 --------------
 src/camel/camel-mapi-notifications.h             |   36 --
 src/camel/camel-mapi-private.h                   |   46 ---
 src/camel/camel-mapi-provider.c                  |    2 +
 src/camel/camel-mapi-store.c                     |  435 ++++++++++++++++++++--
 src/camel/camel-mapi-store.h                     |    2 -
 src/libexchangemapi/camel-mapi-settings.c        |   49 +++-
 src/libexchangemapi/camel-mapi-settings.h        |    5 +
 src/libexchangemapi/e-mapi-connection.c          |  308 +++++++++++++---
 src/libexchangemapi/e-mapi-connection.h          |   20 +-
 17 files changed, 1009 insertions(+), 525 deletions(-)
---
diff --git a/src/account-setup-eplugin/e-mapi-account-setup.c b/src/account-setup-eplugin/e-mapi-account-setup.c
index 8cb042c..d009229 100644
--- a/src/account-setup-eplugin/e-mapi-account-setup.c
+++ b/src/account-setup-eplugin/e-mapi-account-setup.c
@@ -35,6 +35,7 @@
 #include <libedataserverui/e-passwords.h>
 #include <libedataserver/e-account.h>
 #include <e-util/e-dialog-utils.h>
+#include <e-util/e-plugin-util.h>
 #include "mail/em-config.h"
 #include "e-mapi-account-setup.h"
 #include <addressbook/gui/widgets/eab-config.h>
@@ -708,6 +709,8 @@ e_mapi_create (GtkWidget *parent, ESource *source, EMapiFolderType folder_type)
 		return NULL;
 	}
 
+	e_plugin_util_add_check (parent, _("Lis_ten for server notifications"), source, "server-notification", "true", NULL);
+
 	folders = NULL;
 	group = e_source_peek_group (source);
 	profile = g_strdup (e_source_get_property (source, "profile"));
diff --git a/src/addressbook/e-book-backend-mapi-contacts.c b/src/addressbook/e-book-backend-mapi-contacts.c
index 076763d..8017276 100644
--- a/src/addressbook/e-book-backend-mapi-contacts.c
+++ b/src/addressbook/e-book-backend-mapi-contacts.c
@@ -584,6 +584,74 @@ gather_known_uids_cb (EMapiConnection *conn,
 }
 
 static void
+ebbmc_server_notification_cb (EMapiConnection *conn,
+			      guint event_mask,
+			      gpointer event_data,
+			      gpointer user_data)
+{
+	EBookBackendMAPI *ebma = user_data;
+	EBookBackendMAPIContactsPrivate *priv;
+	mapi_id_t update_folder1 = 0, update_folder2 = 0;
+
+	g_return_if_fail (ebma != NULL);
+
+	switch (event_mask) {
+	case fnevNewMail:
+	case fnevNewMail | fnevMbit: {
+		struct NewMailNotification *newmail = event_data;
+
+		if (newmail)
+			update_folder1 = newmail->FID;
+		} break;
+	case fnevObjectCreated:
+	case fnevMbit | fnevObjectCreated: {
+		struct MessageCreatedNotification *msgcreated = event_data;
+
+		if (msgcreated)
+			update_folder1 = msgcreated->FID;
+		} break;
+	case fnevObjectModified:
+	case fnevMbit | fnevObjectModified: {
+		struct MessageModifiedNotification *msgmodified = event_data;
+
+		if (msgmodified)
+			update_folder1 = msgmodified->FID;
+		} break;
+	case fnevObjectDeleted:
+	case fnevMbit | fnevObjectDeleted: {
+		struct MessageDeletedNotification *msgdeleted = event_data;
+
+		if (msgdeleted)
+			update_folder1 = msgdeleted->FID;
+		} break;
+	case fnevObjectMoved:
+	case fnevMbit | fnevObjectMoved: {
+		struct MessageMoveCopyNotification *msgmoved = event_data;
+
+		if (msgmoved) {
+			update_folder1 = msgmoved->OldFID;
+			update_folder2 = msgmoved->FID;
+		}
+		} break;
+	case fnevObjectCopied:
+	case fnevMbit | fnevObjectCopied: {
+		struct MessageMoveCopyNotification *msgcopied = event_data;
+
+		if (msgcopied) {
+			update_folder1 = msgcopied->OldFID;
+			update_folder2 = msgcopied->FID;
+		}
+		} break;
+	default:
+		break;
+	}
+
+	priv = ((EBookBackendMAPIContacts *) ebma)->priv;
+	if (priv->fid == update_folder1 || priv->fid == update_folder2)
+		e_book_backend_mapi_refresh_cache (ebma);
+}
+
+static void
 ebbm_contacts_open (EBookBackendMAPI *ebma, GCancellable *cancellable, gboolean only_if_exists, GError **perror)
 {
 	ESource *source = e_backend_get_source (E_BACKEND (ebma));
@@ -612,7 +680,45 @@ ebbm_contacts_open (EBookBackendMAPI *ebma, GCancellable *cancellable, gboolean
 static void
 ebbm_contacts_connection_status_changed (EBookBackendMAPI *ebma, gboolean is_online)
 {
+	ESource *source;
+	EBookBackendMAPIContactsPrivate *priv = ((EBookBackendMAPIContacts *) ebma)->priv;
+
 	e_book_backend_notify_readonly (E_BOOK_BACKEND (ebma), !is_online);
+
+	if (!is_online)
+		return;
+
+	source = e_backend_get_source (E_BACKEND (ebma));
+	if (source && g_strcmp0 (e_source_get_property (source, "server-notification"), "true") == 0) {
+		EMapiConnection *conn;
+		mapi_object_t obj_folder;
+		gboolean status;
+
+		e_book_backend_mapi_lock_connection (ebma);
+
+		conn = e_book_backend_mapi_get_connection (ebma);
+		if (!conn) {
+			e_book_backend_mapi_unlock_connection (ebma);
+			return;
+		}
+
+		if (priv->is_public_folder)
+			status = e_mapi_connection_open_public_folder (conn, priv->fid, &obj_folder, NULL, NULL);
+		else
+			status = e_mapi_connection_open_personal_folder (conn, priv->fid, &obj_folder, NULL, NULL);
+
+		if (status) {
+			e_mapi_connection_enable_notifications (conn, &obj_folder,
+				fnevObjectCreated | fnevObjectModified | fnevObjectDeleted | fnevObjectMoved | fnevObjectCopied,
+				NULL, NULL);
+
+			e_mapi_connection_close_folder (conn, &obj_folder, NULL, NULL);
+		}
+
+		g_signal_connect (conn, "server-notification", G_CALLBACK (ebbmc_server_notification_cb), ebma);
+
+		e_book_backend_mapi_unlock_connection (ebma);
+	}
 }
 
 static void
diff --git a/src/addressbook/e-book-backend-mapi.c b/src/addressbook/e-book-backend-mapi.c
index bf28e95..c344310 100644
--- a/src/addressbook/e-book-backend-mapi.c
+++ b/src/addressbook/e-book-backend-mapi.c
@@ -65,6 +65,7 @@ struct _EBookBackendMAPIPrivate
 
 	guint32 last_server_contact_count;
 	time_t last_modify_time;
+	gboolean server_dirty;
 };
 
 #define ELEMENT_TYPE_MASK   0xF /* mask where the real type of the element is stored */
@@ -219,7 +220,6 @@ ebbm_update_cache_cb (gpointer data)
 	EBookBackendMAPIPrivate *priv;
 	EBookBackendMAPIClass *ebmac;
 	guint32 server_stored_contacts = 0;
-	GHashTable *local_known_uids, *server_known_uids;
 	time_t restr_tt = 0;
 	gboolean partial_update = FALSE;
 	GCancellable *cancellable;
@@ -236,76 +236,83 @@ ebbm_update_cache_cb (gpointer data)
 	ebmac = E_BOOK_BACKEND_MAPI_GET_CLASS (ebma);
 	g_return_val_if_fail (ebmac != NULL, NULL);
 
+
 	cancellable = priv->update_cache;
 	g_cancellable_reset (cancellable);
 
-	local_known_uids = e_book_backend_sqlitedb_get_uids_and_rev (priv->db, EMA_EBB_CACHE_FOLDERID, &error);
-	server_known_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
-
-	if (!error && !g_cancellable_is_cancelled (cancellable) && ebmac->op_get_contacts_count) {
-		ebmac->op_get_contacts_count (ebma, &server_stored_contacts, cancellable, &error);
-	}
+	do {
+		GHashTable *local_known_uids, *server_known_uids;
 
-	if (!error && !g_cancellable_is_cancelled (cancellable) && ebmac->op_list_known_uids) {
-		struct ListKnownUidsData lku;
+		priv->server_dirty = FALSE;
 
-		restr_tt = priv->last_modify_time && server_stored_contacts == g_hash_table_size (local_known_uids) ? priv->last_modify_time + 1 : 0;
-		partial_update = restr_tt > 0;
+		local_known_uids = e_book_backend_sqlitedb_get_uids_and_rev (priv->db, EMA_EBB_CACHE_FOLDERID, &error);
+		server_known_uids = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
 
-		lku.uid_to_rev = server_known_uids;
-		lku.latest_last_modify = priv->last_modify_time;
+		if (!error && !g_cancellable_is_cancelled (cancellable) && ebmac->op_get_contacts_count) {
+			ebmac->op_get_contacts_count (ebma, &server_stored_contacts, cancellable, &error);
+		}
 
-		ebmac->op_list_known_uids (ebma, partial_update ? e_mapi_utils_build_last_modify_restriction : NULL, &restr_tt, &lku, cancellable, &error);
+		if (!error && !g_cancellable_is_cancelled (cancellable) && ebmac->op_list_known_uids) {
+			struct ListKnownUidsData lku;
 
-		restr_tt = lku.latest_last_modify;
-	}
+			restr_tt = priv->last_modify_time && server_stored_contacts == g_hash_table_size (local_known_uids) ? priv->last_modify_time + 1 : 0;
+			partial_update = restr_tt > 0;
 
-	if (!error && !g_cancellable_is_cancelled (cancellable) && ebmac->op_transfer_contacts && local_known_uids) {
-		GSList *uids = NULL;
-		GHashTableIter iter;
-		gpointer key, value;
+			lku.uid_to_rev = server_known_uids;
+			lku.latest_last_modify = priv->last_modify_time;
 
-		g_hash_table_iter_init (&iter, server_known_uids);
-		while (g_hash_table_iter_next (&iter, &key, &value)) {
-			const gchar *uid = key, *rev = value, *local_rev;
+			ebmac->op_list_known_uids (ebma, partial_update ? e_mapi_utils_build_last_modify_restriction : NULL, &restr_tt, &lku, cancellable, &error);
 
-			local_rev = g_hash_table_lookup (local_known_uids, uid);
-			if (g_strcmp0 (local_rev, rev) != 0) {
-				uids = g_slist_prepend (uids, (gpointer) uid);
-			}
-
-			g_hash_table_remove (local_known_uids, uid);
+			restr_tt = lku.latest_last_modify;
 		}
 
-		if (uids)
-			ebbm_transfer_contacts (ebma, uids, NULL, cancellable, &error);
+		if (!error && !g_cancellable_is_cancelled (cancellable) && ebmac->op_transfer_contacts && local_known_uids) {
+			GSList *uids = NULL;
+			GHashTableIter iter;
+			gpointer key, value;
 
-		if (!error && !g_cancellable_is_cancelled (cancellable) && !partial_update) {
-			g_hash_table_iter_init (&iter, local_known_uids);
+			g_hash_table_iter_init (&iter, server_known_uids);
 			while (g_hash_table_iter_next (&iter, &key, &value)) {
-				const gchar *uid = key;
+				const gchar *uid = key, *rev = value, *local_rev;
 
-				if (!uid)
-					continue;
+				local_rev = g_hash_table_lookup (local_known_uids, uid);
+				if (g_strcmp0 (local_rev, rev) != 0) {
+					uids = g_slist_prepend (uids, (gpointer) uid);
+				}
 
-				e_book_backend_mapi_notify_contact_removed (ebma, uid);
+				g_hash_table_remove (local_known_uids, uid);
 			}
-		}
 
-		priv->last_server_contact_count = server_stored_contacts;
-		priv->last_modify_time = restr_tt;
+			if (uids)
+				ebbm_transfer_contacts (ebma, uids, NULL, cancellable, &error);
 
-		/* has borrowed data from server_known_uids */
-		g_slist_free (uids);
-	}
+			if (!error && !g_cancellable_is_cancelled (cancellable) && !partial_update) {
+				g_hash_table_iter_init (&iter, local_known_uids);
+				while (g_hash_table_iter_next (&iter, &key, &value)) {
+					const gchar *uid = key;
 
-	priv->last_update_cache = time(NULL);
+					if (!uid)
+						continue;
 
-	g_hash_table_destroy (server_known_uids);
-	if (local_known_uids)
-		g_hash_table_destroy (local_known_uids);
-	if (error)
-		g_error_free (error);
+					e_book_backend_mapi_notify_contact_removed (ebma, uid);
+				}
+			}
+
+			priv->last_server_contact_count = server_stored_contacts;
+			priv->last_modify_time = restr_tt;
+
+			/* has borrowed data from server_known_uids */
+			g_slist_free (uids);
+		}
+
+		priv->last_update_cache = time(NULL);
+
+		g_hash_table_destroy (server_known_uids);
+		if (local_known_uids)
+			g_hash_table_destroy (local_known_uids);
+	} while (!error && priv->server_dirty && !g_cancellable_is_cancelled (cancellable));
+
+	g_clear_error (&error);
 
 	complete_views (ebma);
 
@@ -340,6 +347,8 @@ ebbm_maybe_invoke_cache_update (EBookBackendMAPI *ebma)
 	if (time (NULL) - priv->last_update_cache >= 60 * 10) {
 		g_object_ref (ebma);
 
+		g_cancellable_reset (priv->update_cache);
+		priv->server_dirty = FALSE;
 		priv->update_cache_thread = g_thread_create (ebbm_update_cache_cb, ebma, TRUE, NULL);
 		if (!priv->update_cache_thread)
 			g_object_unref (ebma);
@@ -1267,6 +1276,7 @@ e_book_backend_mapi_init (EBookBackendMAPI *ebma)
 	ebma->priv->last_update_cache = 0;
 	ebma->priv->last_server_contact_count = 0;
 	ebma->priv->last_modify_time = 0;
+	ebma->priv->server_dirty = FALSE;
 
 	g_signal_connect (
 		ebma, "notify::online",
@@ -1590,6 +1600,19 @@ e_book_backend_mapi_cache_get (EBookBackendMAPI *ebma, const gchar *key)
 	return e_book_backend_sqlitedb_get_key_value (ebma->priv->db, EMA_EBB_CACHE_FOLDERID, key, NULL);
 }
 
+void
+e_book_backend_mapi_refresh_cache (EBookBackendMAPI *ebma)
+{
+	g_return_if_fail (ebma != NULL);
+	g_return_if_fail (E_IS_BOOK_BACKEND_MAPI (ebma));
+
+	ebma->priv->last_update_cache = 0;
+	ebma->priv->last_modify_time = 0;
+	ebma->priv->server_dirty = TRUE;
+
+	ebbm_maybe_invoke_cache_update (ebma);
+}
+
 /* utility functions/macros */
 
 /* 'data' is one of GET_ALL_KNOWN_IDS or GET_UIDS_ONLY */
@@ -1734,9 +1757,21 @@ mapi_book_utils_contact_from_props (EMapiConnection *conn, mapi_id_t fid, const
 
 	if (g_str_equal (get_str_proptag (PR_MESSAGE_CLASS), IPM_DISTLIST)) {
 		const struct mapi_SBinaryArray *members, *members_dlist;
+		const struct FILETIME *last_modification;
 		GSList *attrs = NULL, *a;
 		gint i;
 
+		last_modification = get_proptag (PidTagLastModificationTime);
+		if (last_modification) {
+			gchar *buff = NULL;
+
+			buff = mapi_book_utils_timet_to_string (e_mapi_util_filetime_to_time_t (last_modification));
+			if (buff)
+				e_contact_set (contact, E_CONTACT_REV, buff);
+
+			g_free (buff);
+		}
+
 		/* it's a contact list/distribution list, fetch members and return it */
 		e_contact_set (contact, E_CONTACT_IS_LIST, GINT_TO_POINTER (TRUE));
 		/* we do not support this option, same as GroupWise */
diff --git a/src/addressbook/e-book-backend-mapi.h b/src/addressbook/e-book-backend-mapi.h
index 4e84bcb..504312e 100644
--- a/src/addressbook/e-book-backend-mapi.h
+++ b/src/addressbook/e-book-backend-mapi.h
@@ -112,6 +112,7 @@ gboolean e_book_backend_mapi_notify_contact_update (EBookBackendMAPI *ebma, EDat
 void e_book_backend_mapi_notify_contact_removed (EBookBackendMAPI *ebma, const gchar *uid);
 void   e_book_backend_mapi_cache_set (EBookBackendMAPI *ebma, const gchar *key, const gchar *value);
 gchar *e_book_backend_mapi_cache_get (EBookBackendMAPI *ebma, const gchar *key);
+void e_book_backend_mapi_refresh_cache (EBookBackendMAPI *ebma);
 
 /* utility functions/macros */
 
diff --git a/src/calendar/e-cal-backend-mapi.c b/src/calendar/e-cal-backend-mapi.c
index 0b29951..656c541 100644
--- a/src/calendar/e-cal-backend-mapi.c
+++ b/src/calendar/e-cal-backend-mapi.c
@@ -70,6 +70,7 @@ struct _ECalBackendMAPIPrivate {
 	mapi_id_t		fid;
 	uint32_t		olFolder;
 	gchar			*profile;
+	gboolean is_public_folder;
 	EMapiConnection *conn;
 
 	/* These fields are entirely for access rights */
@@ -286,18 +287,15 @@ ecbm_remove (ECalBackend *backend, EDataCal *cal, GCancellable *cancellable, GEr
 {
 	ECalBackendMAPI *cbmapi;
 	ECalBackendMAPIPrivate *priv;
-	ESource *source = NULL;
 
 	cbmapi = E_CAL_BACKEND_MAPI (backend);
 	priv = cbmapi->priv;
 
-	source = e_backend_get_source (E_BACKEND (cbmapi));
-
 	if (!e_backend_get_online (E_BACKEND (backend)) || !priv->conn || !e_mapi_connection_connected (priv->conn)) {
 		g_propagate_error (perror, EDC_ERROR (RepositoryOffline));
 		return;
 	}
-	if (g_strcmp0 (e_source_get_property (source, "public"), "yes") != 0) {
+	if (!priv->is_public_folder) {
 		GError *mapi_error = NULL;
 
 		if (!e_mapi_connection_remove_folder (priv->conn, priv->fid, 0, cancellable, &mapi_error)) {
@@ -694,7 +692,6 @@ update_local_cache (ECalBackendMAPI *cbmapi, GCancellable *cancellable)
 {
 	ECalBackendMAPIPrivate *priv;
 	EMapiConnection *conn;
-	ESource *source = NULL;
 	struct ListCalendarObjectsData lco;
 	GSList *iter, *components;
 	mapi_object_t obj_folder;
@@ -705,7 +702,6 @@ update_local_cache (ECalBackendMAPI *cbmapi, GCancellable *cancellable)
 	struct FolderBasicPropertiesData fbp;
 
 	priv = cbmapi->priv;
-	source = e_backend_get_source (E_BACKEND (cbmapi));
 	if (!e_backend_get_online (E_BACKEND (cbmapi)))
 		return FALSE;
 
@@ -717,7 +713,7 @@ update_local_cache (ECalBackendMAPI *cbmapi, GCancellable *cancellable)
 
 	conn = g_object_ref (priv->conn);
 
-	if (g_strcmp0 (e_source_get_property (source, "public"), "yes") == 0) {
+	if (priv->is_public_folder) {
 		success = e_mapi_connection_open_public_folder (conn, priv->fid, &obj_folder, cancellable, &mapi_error);
 	} else {
 		success = e_mapi_connection_open_personal_folder (conn, priv->fid, &obj_folder, cancellable, &mapi_error);
@@ -1057,6 +1053,74 @@ ecbm_connect (ECalBackendMAPI *cbmapi, GError **perror)
 }
 
 static void
+ecbm_server_notification_cb (EMapiConnection *conn,
+			     guint event_mask,
+			     gpointer event_data,
+			     gpointer user_data)
+{
+	ECalBackendMAPI *cbmapi = user_data;
+	ECalBackendMAPIPrivate *priv;
+	mapi_id_t update_folder1 = 0, update_folder2 = 0;
+
+	g_return_if_fail (cbmapi != NULL);
+
+	switch (event_mask) {
+	case fnevNewMail:
+	case fnevNewMail | fnevMbit: {
+		struct NewMailNotification *newmail = event_data;
+
+		if (newmail)
+			update_folder1 = newmail->FID;
+		} break;
+	case fnevObjectCreated:
+	case fnevMbit | fnevObjectCreated: {
+		struct MessageCreatedNotification *msgcreated = event_data;
+
+		if (msgcreated)
+			update_folder1 = msgcreated->FID;
+		} break;
+	case fnevObjectModified:
+	case fnevMbit | fnevObjectModified: {
+		struct MessageModifiedNotification *msgmodified = event_data;
+
+		if (msgmodified)
+			update_folder1 = msgmodified->FID;
+		} break;
+	case fnevObjectDeleted:
+	case fnevMbit | fnevObjectDeleted: {
+		struct MessageDeletedNotification *msgdeleted = event_data;
+
+		if (msgdeleted)
+			update_folder1 = msgdeleted->FID;
+		} break;
+	case fnevObjectMoved:
+	case fnevMbit | fnevObjectMoved: {
+		struct MessageMoveCopyNotification *msgmoved = event_data;
+
+		if (msgmoved) {
+			update_folder1 = msgmoved->OldFID;
+			update_folder2 = msgmoved->FID;
+		}
+		} break;
+	case fnevObjectCopied:
+	case fnevMbit | fnevObjectCopied: {
+		struct MessageMoveCopyNotification *msgcopied = event_data;
+
+		if (msgcopied) {
+			update_folder1 = msgcopied->OldFID;
+			update_folder2 = msgcopied->FID;
+		}
+		} break;
+	default:
+		break;
+	}
+
+	priv = cbmapi->priv;
+	if (priv->fid == update_folder1 || priv->fid == update_folder2)
+		run_delta_thread (cbmapi);
+}
+
+static void
 ecbm_connect_user (ECalBackend *backend, GCancellable *cancellable, const gchar *password, GError **perror)
 {
 	ECalBackendMAPI *cbmapi;
@@ -1084,7 +1148,29 @@ ecbm_connect_user (ECalBackend *backend, GCancellable *cancellable, const gchar
 		g_object_unref (old_conn);
 
 	if (priv->conn && e_mapi_connection_connected (priv->conn)) {
-		/* Success */;
+		/* Success */
+		ESource *source;
+
+		source = e_backend_get_source (E_BACKEND (cbmapi));
+		if (source && g_strcmp0 (e_source_get_property (source, "server-notification"), "true") == 0) {
+			mapi_object_t obj_folder;
+			gboolean status;
+
+			if (priv->is_public_folder)
+				status = e_mapi_connection_open_public_folder (priv->conn, priv->fid, &obj_folder, NULL, NULL);
+			else
+				status = e_mapi_connection_open_personal_folder (priv->conn, priv->fid, &obj_folder, NULL, NULL);
+
+			if (status) {
+				e_mapi_connection_enable_notifications (priv->conn, &obj_folder,
+					fnevObjectCreated | fnevObjectModified | fnevObjectDeleted | fnevObjectMoved | fnevObjectCopied,
+					NULL, NULL);
+
+				e_mapi_connection_close_folder (priv->conn, &obj_folder, NULL, NULL);
+			}
+
+			g_signal_connect (priv->conn, "server-notification", G_CALLBACK (ecbm_server_notification_cb), cbmapi);
+		}
 	} else {
 		mapi_error_to_edc_error (perror, mapi_error, g_error_matches (mapi_error, E_MAPI_ERROR, MAPI_E_NETWORK_ERROR) ? OtherError : AuthenticationFailed, NULL);
 		if (mapi_error)
@@ -1197,6 +1283,7 @@ ecbm_open (ECalBackend *backend, EDataCal *cal, GCancellable *cancellable, gbool
 
 	e_mapi_util_mapi_id_from_string (fid, &priv->fid);
 	priv->olFolder = olFolder;
+	priv->is_public_folder = g_strcmp0 (e_source_get_property (esource, "public"), "yes") == 0;
 
 	krb_sso = e_source_get_property (esource, "kerberos");
 	g_mutex_unlock (priv->mutex);
diff --git a/src/camel/Makefile.am b/src/camel/Makefile.am
index d2cb200..ce4cc97 100644
--- a/src/camel/Makefile.am
+++ b/src/camel/Makefile.am
@@ -19,7 +19,6 @@ libcamelmapi_la_SOURCES = 			\
 	camel-mapi-folder-summary.c		\
         camel-mapi-store.c	                \
 	camel-mapi-store-summary.c         	\
-	camel-mapi-notifications.c		\
 	camel-mapi-transport.c			
 
 noinst_HEADERS =         			\
@@ -27,9 +26,7 @@ noinst_HEADERS =         			\
 	camel-mapi-folder-summary.h		\
 	camel-mapi-store.h			\
 	camel-mapi-store-summary.h         	\
-	camel-mapi-transport.h			\
-	camel-mapi-notifications.h		\
-	camel-mapi-private.h
+	camel-mapi-transport.h
 
 libcamelmapi_la_LDFLAGS = -avoid-version -module $(NO_UNDEFINED)
 libcamelmapi_la_LIBADD = 			\
diff --git a/src/camel/camel-mapi-folder.c b/src/camel/camel-mapi-folder.c
index 646ab39..f44ffe4 100644
--- a/src/camel/camel-mapi-folder.c
+++ b/src/camel/camel-mapi-folder.c
@@ -40,13 +40,17 @@
 #include "camel-mapi-store.h"
 #include "camel-mapi-store-summary.h"
 #include "camel-mapi-folder.h"
-#include "camel-mapi-private.h"
 #include "camel-mapi-folder-summary.h"
 
 #define DEBUG_FN( ) printf("----%p %s\n", g_thread_self(), G_STRFUNC);
 #define SUMMARY_FETCH_BATCH_COUNT 150
 #define d(x)
 
+#define CAMEL_MAPI_FOLDER_LOCK(f, l) \
+	(g_static_mutex_lock(&((CamelMapiFolder *)f)->priv->l))
+#define CAMEL_MAPI_FOLDER_UNLOCK(f, l) \
+	(g_static_mutex_unlock(&((CamelMapiFolder *)f)->priv->l))
+
 extern gint camel_application_is_exiting;
 
 struct _CamelMapiFolderPrivate {
@@ -460,7 +464,8 @@ gather_changed_objects_to_slist (EMapiConnection *conn,
 		if (info) {
 			CamelMapiMessageInfo *minfo = (CamelMapiMessageInfo *) info;
 
-			if (minfo->last_modified != object_data->last_modified) {
+			if (minfo->last_modified != object_data->last_modified
+			    && (object_data->msg_flags & MSGFLAG_UNMODIFIED) == 0) {
 				update = TRUE;
 			} else {
 				guint32 mask = CAMEL_MESSAGE_SEEN | CAMEL_MESSAGE_ATTACHMENTS, flags = 0;
diff --git a/src/camel/camel-mapi-provider.c b/src/camel/camel-mapi-provider.c
index 47a9b8f..e0a399f 100644
--- a/src/camel/camel-mapi-provider.c
+++ b/src/camel/camel-mapi-provider.c
@@ -57,6 +57,8 @@ static CamelProviderConfEntry mapi_conf_entries[] = {
 	  N_("Check new messages for _Junk contents"), "0" },
 	{ CAMEL_PROVIDER_CONF_CHECKBOX, "filter-junk-inbox", "filter-junk",
 	  N_("Only check for Junk messag_es in the Inbox folder"), "0" },
+	{ CAMEL_PROVIDER_CONF_CHECKBOX, "listen-notifications", NULL,
+	  N_("Lis_ten for server notifications"), "0" },
 
 	{ CAMEL_PROVIDER_CONF_SECTION_END },
 	{ CAMEL_PROVIDER_CONF_END }
diff --git a/src/camel/camel-mapi-store.c b/src/camel/camel-mapi-store.c
index a513910..97216d0 100644
--- a/src/camel/camel-mapi-store.c
+++ b/src/camel/camel-mapi-store.c
@@ -25,11 +25,17 @@
 #include <config.h>
 #endif
 
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
 #include <ctype.h>
+#include <sys/types.h>
 #include <errno.h>
 
+#include <libmapi/libmapi.h>
+
 #include <glib/gi18n-lib.h>
 #include <glib/gstdio.h>
 
@@ -38,18 +44,10 @@
 #include "camel-mapi-settings.h"
 #include "camel-mapi-store-summary.h"
 #include "camel-mapi-folder-summary.h"
-#include "camel-mapi-notifications.h"
 #include "account-setup-eplugin/e-mapi-account-listener.h"
 #include <e-mapi-utils.h>
 
-#include <sys/types.h>
-#include <stdint.h>
-#include <stdbool.h>
-#include <stdlib.h>
-#include <libmapi/libmapi.h>
-#include <param.h>
-
-#define d(x) printf("%s:%s:%s \n", G_STRLOC, G_STRFUNC, x)
+#define d(x)
 
 struct _CamelMapiStorePrivate {
 	EMapiConnection *conn;
@@ -61,7 +59,12 @@ struct _CamelMapiStorePrivate {
 	GHashTable *default_folders; /*Default Type : Folder ID*/
 
 	gboolean folders_synced; /* whether were synced folder list already */
-	gpointer notification_data; /* pointer to a notification data; can be only one */
+
+	GStaticRecMutex updates_lock;
+	GCancellable *updates_cancellable; /* cancelled on dispose or disconnect */
+	GSList *update_folder_names; /* gchar *foldername */
+	guint update_folder_id;
+	guint update_folder_list_id;
 };
 
 /* Forward Declarations */
@@ -82,6 +85,7 @@ static gboolean	mapi_connect_sync(CamelService *, GCancellable *cancellable, GEr
 static gboolean	mapi_disconnect_sync(CamelService *, gboolean , GCancellable *cancellable, GError **);
 static CamelAuthenticationResult mapi_authenticate_sync (CamelService *, const gchar *mechanism, GCancellable *, GError **);
 static GList	*mapi_query_auth_types_sync(CamelService *, GCancellable *cancellable, GError **);
+static void camel_mapi_store_server_notification_cb (EMapiConnection *conn, guint event_mask, gpointer event_data, gpointer user_data);
 
 /* store methods */
 static CamelFolderInfo * mapi_build_folder_info(CamelMapiStore *mapi_store, const gchar *parent_name, const gchar *folder_name);
@@ -797,13 +801,52 @@ mapi_rename_folder_infos (CamelMapiStore *mapi_store, const gchar *old_name, con
 }
 
 static void
+stop_pending_updates (CamelMapiStore *mapi_store)
+{
+	CamelMapiStorePrivate *priv;
+
+	g_return_if_fail (mapi_store != NULL);
+	g_return_if_fail (mapi_store->priv != NULL);
+
+	priv = mapi_store->priv;
+
+	g_static_rec_mutex_lock (&priv->updates_lock);
+	if (priv->updates_cancellable) {
+		g_cancellable_cancel (priv->updates_cancellable);
+		g_object_unref (priv->updates_cancellable);
+		priv->updates_cancellable = NULL;
+	}
+
+	if (priv->update_folder_names) {
+		g_slist_free_full (priv->update_folder_names, g_free);
+		priv->update_folder_names = NULL;
+	}
+
+	if (priv->update_folder_id) {
+		g_source_remove (priv->update_folder_id);
+		priv->update_folder_id = 0;
+	}
+
+	if (priv->update_folder_list_id) {
+		g_source_remove (priv->update_folder_list_id);
+		priv->update_folder_list_id = 0;
+	}
+
+	g_static_rec_mutex_unlock (&priv->updates_lock);
+}
+
+static void
 mapi_store_dispose (GObject *object)
 {
 	CamelMapiStorePrivate *priv;
 
 	priv = CAMEL_MAPI_STORE (object)->priv;
 
+	stop_pending_updates (CAMEL_MAPI_STORE (object));
+
 	if (priv->conn != NULL) {
+		g_signal_handlers_disconnect_by_func (priv->conn, camel_mapi_store_server_notification_cb, object);
+
 		g_object_unref (priv->conn);
 		priv->conn = NULL;
 	}
@@ -833,6 +876,8 @@ mapi_store_finalize (GObject *object)
 	if (priv->container_hash != NULL)
 		g_hash_table_destroy (priv->container_hash);
 
+	g_static_rec_mutex_free (&priv->updates_lock);
+
 	/* Chain up to parent's finalize() method. */
 	G_OBJECT_CLASS (camel_mapi_store_parent_class)->finalize (object);
 }
@@ -1600,6 +1645,12 @@ static void
 camel_mapi_store_init (CamelMapiStore *mapi_store)
 {
 	mapi_store->priv = G_TYPE_INSTANCE_GET_PRIVATE (mapi_store, CAMEL_TYPE_MAPI_STORE, CamelMapiStorePrivate);
+
+	g_static_rec_mutex_init (&mapi_store->priv->updates_lock);
+	mapi_store->priv->updates_cancellable = NULL;
+	mapi_store->priv->update_folder_names = NULL;
+	mapi_store->priv->update_folder_id = 0;
+	mapi_store->priv->update_folder_list_id = 0;
 }
 
 /* service methods */
@@ -1677,7 +1728,6 @@ mapi_connect_sync (CamelService *service,
 	CamelServiceConnectionStatus status;
 	CamelSession *session;
 	gchar *name;
-	guint16 event_mask = 0;
 
 	session = camel_service_get_session (service);
 
@@ -1708,14 +1758,6 @@ mapi_connect_sync (CamelService *service,
 	camel_offline_store_set_online_sync (
 		CAMEL_OFFLINE_STORE (store), TRUE, cancellable, NULL);
 
-	/* Start event monitor */
-	event_mask = fnevNewMail | fnevObjectCreated | fnevObjectDeleted |
-		fnevObjectModified | fnevObjectMoved | fnevObjectCopied;
-
-	/* use MAPI_LISTEN_NOTIFY=1 to enable notifications */
-	if (!store->priv->notification_data && g_getenv ("MAPI_LISTEN_NOTIFY") != NULL)
-		store->priv->notification_data = camel_mapi_notification_listener_start (store, event_mask, MAPI_EVENTS_USE_STORE);
-
 	camel_store_summary_save (store->summary);
 
 	camel_service_unlock (service, CAMEL_SERVICE_REC_CONNECT_LOCK);
@@ -1723,15 +1765,6 @@ mapi_connect_sync (CamelService *service,
 	return TRUE;
 }
 
-void
-camel_mapi_store_unset_notification_data (CamelMapiStore *mstore)
-{
-	g_return_if_fail (mstore != NULL);
-	g_return_if_fail (CAMEL_IS_MAPI_STORE (mstore));
-
-	mstore->priv->notification_data = NULL;
-}
-
 static gboolean
 mapi_disconnect_sync (CamelService *service,
                       gboolean clean,
@@ -1740,13 +1773,12 @@ mapi_disconnect_sync (CamelService *service,
 {
 	CamelMapiStore *store = CAMEL_MAPI_STORE (service);
 
-	/* Disconnect from event monitor */
-	if (store->priv->notification_data) {
-		camel_mapi_notification_listener_stop (store, store->priv->notification_data);
-		store->priv->notification_data = NULL;
-	}
+	stop_pending_updates (store);
 
 	if (store->priv->conn) {
+		g_signal_handlers_disconnect_by_func (store->priv->conn, camel_mapi_store_server_notification_cb, store);
+		e_mapi_connection_disable_notifications (store->priv->conn, NULL, cancellable, error);
+
 		/* Close the mapi subsystem */
 		g_object_unref (store->priv->conn);
 		store->priv->conn = NULL;
@@ -1757,6 +1789,335 @@ mapi_disconnect_sync (CamelService *service,
 	return TRUE;
 }
 
+struct ScheduleUpdateData
+{
+	GCancellable *cancellable;
+	CamelMapiStore *mapi_store;
+	GSList *foldernames;
+	guint expected_id;
+};
+
+static void
+free_schedule_update_data (gpointer ptr)
+{
+	struct ScheduleUpdateData *sud = ptr;
+
+	if (!sud)
+		return;
+
+	if (sud->cancellable)
+		g_object_unref (sud->cancellable);
+	g_slist_free_full (sud->foldernames, g_free);
+	g_free (sud);
+}
+
+static gpointer
+camel_mapi_folder_update_thread (gpointer user_data)
+{
+	struct ScheduleUpdateData *sud = user_data;
+	CamelMapiStore *mapi_store;
+	GSList *fn;
+
+	g_return_val_if_fail (sud != NULL, NULL);
+
+	mapi_store = g_object_ref (sud->mapi_store);
+
+	for (fn = sud->foldernames; fn && !g_cancellable_is_cancelled (sud->cancellable); fn = fn->next) {
+		const gchar *foldername = fn->data;
+		CamelFolder *folder;
+
+		if (!foldername)
+			continue;
+
+		folder = camel_store_get_folder_sync (CAMEL_STORE (mapi_store), foldername, 0, sud->cancellable, NULL);
+		if (folder) {
+			camel_folder_refresh_info_sync (folder, sud->cancellable, NULL);
+			g_object_unref (folder);
+		}
+	}
+
+	if (!g_cancellable_is_cancelled (sud->cancellable) &&
+	    !mapi_store->priv->folders_synced)
+		mapi_folders_sync (sud->mapi_store, CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_SUBSCRIBED, sud->cancellable, NULL);
+
+	g_object_unref (mapi_store);
+
+	free_schedule_update_data (sud);
+
+	return NULL;
+}
+
+static void
+run_update_thread (CamelMapiStore *mapi_store,
+		   GCancellable *cancellable,
+		   GSList *foldernames)
+{
+	struct ScheduleUpdateData *sud;
+
+	g_return_if_fail (mapi_store != NULL);
+	g_return_if_fail (cancellable != NULL);
+
+	sud = g_new0 (struct ScheduleUpdateData, 1);
+	sud->mapi_store = mapi_store;
+	sud->cancellable = g_object_ref (cancellable);
+	sud->foldernames = foldernames;
+
+	g_thread_create (camel_mapi_folder_update_thread, sud, FALSE, NULL);
+}
+
+static gboolean
+folder_update_cb (gpointer user_data)
+{
+	struct ScheduleUpdateData *sud = user_data;
+	GSList *foldernames;
+
+	g_return_val_if_fail (sud != NULL, FALSE);
+
+	if (g_cancellable_is_cancelled (sud->cancellable))
+		return FALSE;
+
+	g_return_val_if_fail (sud->mapi_store != NULL, FALSE);
+	g_return_val_if_fail (sud->mapi_store->priv != NULL, FALSE);
+
+	g_static_rec_mutex_lock (&sud->mapi_store->priv->updates_lock);
+	if (sud->expected_id != sud->mapi_store->priv->update_folder_id) {
+		g_static_rec_mutex_unlock (&sud->mapi_store->priv->updates_lock);
+		return FALSE;
+	}
+
+	foldernames = sud->mapi_store->priv->update_folder_names;
+	sud->mapi_store->priv->update_folder_names = NULL;
+	sud->mapi_store->priv->update_folder_id = 0;
+
+	if (!g_cancellable_is_cancelled (sud->cancellable))
+		run_update_thread (sud->mapi_store, sud->cancellable, foldernames);
+	else
+		g_slist_free_full (foldernames, g_free);
+
+	g_static_rec_mutex_unlock (&sud->mapi_store->priv->updates_lock);
+
+	return FALSE;
+}
+
+static void
+schedule_folder_update (CamelMapiStore *mapi_store, mapi_id_t fid)
+{
+	gchar *fidstr;
+	const gchar *foldername;
+	struct ScheduleUpdateData *sud;
+	CamelStoreInfo *si;
+	CamelMapiStoreInfo *msi;
+
+	g_return_if_fail (mapi_store != NULL);
+	g_return_if_fail (mapi_store->priv != NULL);
+
+	si = camel_mapi_store_summary_get_folder_id (mapi_store->summary, fid);
+	if (!si)
+		return;
+
+	msi = (CamelMapiStoreInfo *) si;
+	if ((msi->mapi_folder_flags & CAMEL_MAPI_STORE_FOLDER_FLAG_MAIL) == 0) {
+		camel_store_summary_info_free (mapi_store->summary, si);
+		return;
+	}
+
+	camel_store_summary_info_free (mapi_store->summary, si);
+
+	fidstr = e_mapi_util_mapi_id_to_string (fid);
+	if (!fidstr)
+		return;
+
+	foldername = camel_mapi_store_folder_lookup (mapi_store, fidstr);
+	g_free (fidstr);
+
+	if (!foldername)
+		return;
+
+	g_static_rec_mutex_lock (&mapi_store->priv->updates_lock);
+	if (!mapi_store->priv->updates_cancellable ||
+	    g_slist_find_custom (mapi_store->priv->update_folder_names, foldername, (GCompareFunc) g_ascii_strcasecmp) != 0) {
+		g_static_rec_mutex_unlock (&mapi_store->priv->updates_lock);
+		return;
+	}
+
+	sud = g_new0 (struct ScheduleUpdateData, 1);
+	sud->cancellable = g_object_ref (mapi_store->priv->updates_cancellable);
+	sud->mapi_store = mapi_store;
+
+	mapi_store->priv->update_folder_names = g_slist_prepend (mapi_store->priv->update_folder_names, g_strdup (foldername));
+	if (mapi_store->priv->update_folder_id)
+		g_source_remove (mapi_store->priv->update_folder_id);
+	mapi_store->priv->update_folder_id = g_timeout_add_seconds_full (G_PRIORITY_LOW, 5, folder_update_cb, sud, free_schedule_update_data);
+	sud->expected_id = mapi_store->priv->update_folder_id;
+
+	g_static_rec_mutex_unlock (&mapi_store->priv->updates_lock);
+}
+
+static gboolean
+folder_list_update_cb (gpointer user_data)
+{
+	struct ScheduleUpdateData *sud = user_data;
+
+	g_return_val_if_fail (sud != NULL, FALSE);
+
+	if (g_cancellable_is_cancelled (sud->cancellable))
+		return FALSE;
+
+	g_return_val_if_fail (sud->mapi_store != NULL, FALSE);
+	g_return_val_if_fail (sud->mapi_store->priv != NULL, FALSE);
+
+	g_static_rec_mutex_lock (&sud->mapi_store->priv->updates_lock);
+	if (sud->expected_id != sud->mapi_store->priv->update_folder_list_id) {
+		g_static_rec_mutex_unlock (&sud->mapi_store->priv->updates_lock);
+		return FALSE;
+	}
+
+	sud->mapi_store->priv->folders_synced = FALSE;
+	sud->mapi_store->priv->update_folder_list_id = 0;
+
+	if (!g_cancellable_is_cancelled (sud->cancellable))
+		run_update_thread (sud->mapi_store, sud->cancellable, NULL);
+
+	g_static_rec_mutex_unlock (&sud->mapi_store->priv->updates_lock);
+
+	return FALSE;
+}
+
+static void
+schedule_folder_list_update (CamelMapiStore *mapi_store)
+{
+	struct ScheduleUpdateData *sud;
+
+	g_return_if_fail (mapi_store != NULL);
+	g_return_if_fail (mapi_store->priv != NULL);
+
+	g_static_rec_mutex_lock (&mapi_store->priv->updates_lock);
+	if (!mapi_store->priv->updates_cancellable) {
+		g_static_rec_mutex_unlock (&mapi_store->priv->updates_lock);
+		return;
+	}
+
+	sud = g_new0 (struct ScheduleUpdateData, 1);
+	sud->cancellable = g_object_ref (mapi_store->priv->updates_cancellable);
+	sud->mapi_store = mapi_store;
+
+	if (mapi_store->priv->update_folder_list_id)
+		g_source_remove (mapi_store->priv->update_folder_list_id);
+	mapi_store->priv->update_folder_list_id = g_timeout_add_seconds_full (G_PRIORITY_LOW, 5, folder_list_update_cb, sud, free_schedule_update_data);
+	sud->expected_id = mapi_store->priv->update_folder_list_id;
+
+	g_static_rec_mutex_unlock (&mapi_store->priv->updates_lock);
+}
+
+static void
+camel_mapi_store_server_notification_cb (EMapiConnection *conn,
+					 guint event_mask,
+					 gpointer event_data,
+					 gpointer user_data)
+{
+	CamelMapiStore *mapi_store = user_data;
+	mapi_id_t update_folder1 = 0, update_folder2 = 0;
+	gboolean update_folder_list = FALSE;
+
+	g_return_if_fail (mapi_store != NULL);
+	g_return_if_fail (mapi_store->priv != NULL);
+
+	switch (event_mask) {
+	/* -- Folder Events -- */
+	case fnevObjectCreated:
+		d (printf ("Event : Folder Created\n"));
+		d (mapidump_foldercreated (event_data, "\t"));
+		update_folder_list = TRUE;
+		break;
+	case fnevObjectDeleted:
+		d (printf ("Event : Folder Deleted\n"));
+		d (mapidump_folderdeleted (event_data, "\t"));
+		update_folder_list = TRUE;
+		break;
+	case fnevObjectMoved:
+		d (printf ("Event : Folder Moved\n"));
+		d (mapidump_foldermoved (event_data, "\t"));
+		update_folder_list = TRUE;
+		break;
+	case fnevObjectCopied:
+		d (printf ("Event : Folder Copied\n"));
+		d (mapidump_foldercopied (event_data, "\t"));
+		update_folder_list = TRUE;
+		break;
+
+	/* -- Message Events -- */
+	case fnevNewMail:
+	case fnevNewMail | fnevMbit: {
+		struct NewMailNotification *newmail = event_data;
+
+		d (printf ("Event : New mail\n"));
+		d (mapidump_newmail (event_data, "\t"));
+
+		if (newmail)
+			update_folder1 = newmail->FID;
+		} break;
+	case fnevMbit | fnevObjectCreated: {
+		struct MessageCreatedNotification *msgcreated = event_data;
+
+		d (printf ("Event : Message created\n"));
+		d (mapidump_messagecreated (event_data, "\t"));
+
+		if (msgcreated)
+			update_folder1 = msgcreated->FID;
+		} break;
+	case fnevMbit | fnevObjectModified: {
+		struct MessageModifiedNotification *msgmodified = event_data;
+
+		d (printf ("Event : Message modified\n"));
+		d (mapidump_messagemodified (event_data, "\t"));
+
+		if (msgmodified)
+			update_folder1 = msgmodified->FID;
+		} break;
+	case fnevMbit | fnevObjectDeleted: {
+		struct MessageDeletedNotification *msgdeleted = event_data;
+
+		d (printf ("Event : Message deleted\n"));
+		d (mapidump_messagedeleted (event_data, "\t"));
+
+		if (msgdeleted)
+			update_folder1 = msgdeleted->FID;
+		} break;
+	case fnevMbit | fnevObjectMoved: {
+		struct MessageMoveCopyNotification *msgmoved = event_data;
+
+		d (printf ("Event : Message moved\n"));
+		d (mapidump_messagemoved (event_data, "\t"));
+
+		if (msgmoved) {
+			update_folder1 = msgmoved->OldFID;
+			update_folder2 = msgmoved->FID;
+		}
+		} break;
+	case fnevMbit | fnevObjectCopied: {
+		struct MessageMoveCopyNotification *msgcopied = event_data;
+
+		d (printf ("Event : Message copied\n"));
+		d (mapidump_messagecopied (event_data, "\t"));
+
+		if (msgcopied) {
+			update_folder1 = msgcopied->OldFID;
+			update_folder2 = msgcopied->FID;
+		}
+		} break;
+	default:
+		/* Unsupported  */
+		break;
+	}
+
+	if (update_folder1 > 0)
+		schedule_folder_update (mapi_store, update_folder1);
+	if (update_folder2 > 0)
+		schedule_folder_update (mapi_store, update_folder2);
+	if (update_folder_list)
+		schedule_folder_list_update (mapi_store);
+}
+
 static CamelAuthenticationResult
 mapi_authenticate_sync (CamelService *service,
                         const gchar *mechanism,
@@ -1803,6 +2164,14 @@ mapi_authenticate_sync (CamelService *service,
 	store->priv->conn = e_mapi_connection_new (profile, password, cancellable, &mapi_error);
 	if (store->priv->conn && e_mapi_connection_connected (store->priv->conn)) {
 		result = CAMEL_AUTHENTICATION_ACCEPTED;
+
+		if (!store->priv->updates_cancellable)
+			store->priv->updates_cancellable = g_cancellable_new ();
+
+		g_signal_connect (store->priv->conn, "server-notification", G_CALLBACK (camel_mapi_store_server_notification_cb), store);
+
+		if (camel_mapi_settings_get_listen_notifications (mapi_settings))
+			e_mapi_connection_enable_notifications (store->priv->conn, NULL, 0, NULL, NULL);
 	} else if (g_error_matches (mapi_error, E_MAPI_ERROR, MAPI_E_LOGON_FAILED)) {
 		g_clear_error (&mapi_error);
 		result = CAMEL_AUTHENTICATION_REJECTED;
diff --git a/src/camel/camel-mapi-store.h b/src/camel/camel-mapi-store.h
index d16c656..809ec48 100644
--- a/src/camel/camel-mapi-store.h
+++ b/src/camel/camel-mapi-store.h
@@ -78,8 +78,6 @@ const gchar *	camel_mapi_store_folder_lookup (CamelMapiStore *mapi_store, const
 const gchar *	camel_mapi_store_system_folder_fid (CamelMapiStore *mapi_store, guint folder_type);
 const gchar *	mapi_folders_hash_table_name_lookup (CamelMapiStore *store, const gchar *fid, gboolean use_cache);
 
-void		camel_mapi_store_unset_notification_data (CamelMapiStore *mstore);
-
 EMapiConnection *	camel_mapi_store_get_connection (CamelMapiStore *mapi_store);
 
 G_END_DECLS
diff --git a/src/libexchangemapi/camel-mapi-settings.c b/src/libexchangemapi/camel-mapi-settings.c
index 7f9c55e..ffeec31 100644
--- a/src/libexchangemapi/camel-mapi-settings.c
+++ b/src/libexchangemapi/camel-mapi-settings.c
@@ -27,6 +27,7 @@ struct _CamelMapiSettingsPrivate {
 	gboolean filter_junk;
 	gboolean filter_junk_inbox;
 	gboolean kerberos;
+	gboolean listen_notifications;
 
 	gchar *domain;
 	gchar *profile;
@@ -46,7 +47,8 @@ enum {
 	PROP_PROFILE,
 	PROP_REALM,
 	PROP_SECURITY_METHOD,
-	PROP_USER
+	PROP_USER,
+	PROP_LISTEN_NOTIFICATIONS
 };
 
 G_DEFINE_TYPE_WITH_CODE (
@@ -134,6 +136,13 @@ mapi_settings_set_property (GObject *object,
 				CAMEL_NETWORK_SETTINGS (object),
 				g_value_get_string (value));
 			return;
+
+		case PROP_LISTEN_NOTIFICATIONS:
+			camel_mapi_settings_set_listen_notifications (
+				CAMEL_MAPI_SETTINGS (object),
+				g_value_get_boolean (value));
+			return;
+
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -229,6 +238,13 @@ mapi_settings_get_property (GObject *object,
 				camel_network_settings_get_user (
 				CAMEL_NETWORK_SETTINGS (object)));
 			return;
+
+		case PROP_LISTEN_NOTIFICATIONS:
+			g_value_set_boolean (
+				value,
+				camel_mapi_settings_get_listen_notifications (
+				CAMEL_MAPI_SETTINGS (object)));
+			return;
 	}
 
 	G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -361,6 +377,18 @@ camel_mapi_settings_class_init (CamelMapiSettingsClass *class)
 			G_PARAM_CONSTRUCT |
 			G_PARAM_STATIC_STRINGS));
 
+	g_object_class_install_property (
+		object_class,
+		PROP_LISTEN_NOTIFICATIONS,
+		g_param_spec_boolean (
+			"listen-notifications",
+			"Listen Notifications",
+			"Whether to listen for server notifications",
+			FALSE,
+			G_PARAM_READWRITE |
+			G_PARAM_CONSTRUCT |
+			G_PARAM_STATIC_STRINGS));
+
 	/* Inherited from CamelNetworkSettings. */
 	g_object_class_override_property (
 		object_class,
@@ -523,3 +551,22 @@ camel_mapi_settings_set_realm (CamelMapiSettings *settings,
 
 	g_object_notify (G_OBJECT (settings), "realm");
 }
+
+gboolean
+camel_mapi_settings_get_listen_notifications (CamelMapiSettings *settings)
+{
+	g_return_val_if_fail (CAMEL_IS_MAPI_SETTINGS (settings), FALSE);
+
+	return settings->priv->listen_notifications;
+}
+
+void
+camel_mapi_settings_set_listen_notifications (CamelMapiSettings *settings,
+					      gboolean listen_notifications)
+{
+	g_return_if_fail (CAMEL_IS_MAPI_SETTINGS (settings));
+
+	settings->priv->listen_notifications = listen_notifications;
+
+	g_object_notify (G_OBJECT (settings), "listen-notifications");
+}
diff --git a/src/libexchangemapi/camel-mapi-settings.h b/src/libexchangemapi/camel-mapi-settings.h
index 4db13cd..64e256c 100644
--- a/src/libexchangemapi/camel-mapi-settings.h
+++ b/src/libexchangemapi/camel-mapi-settings.h
@@ -86,6 +86,11 @@ void		camel_mapi_settings_set_profile	(CamelMapiSettings *settings,
 const gchar *	camel_mapi_settings_get_realm	(CamelMapiSettings *settings);
 void		camel_mapi_settings_set_realm	(CamelMapiSettings *settings,
 						 const gchar *realm);
+gboolean	camel_mapi_settings_get_listen_notifications
+						(CamelMapiSettings *settings);
+void		camel_mapi_settings_set_listen_notifications
+						(CamelMapiSettings *settings,
+						 gboolean listen_notifications);
 
 G_END_DECLS
 
diff --git a/src/libexchangemapi/e-mapi-connection.c b/src/libexchangemapi/e-mapi-connection.c
index 30285cc..db7b2de 100644
--- a/src/libexchangemapi/e-mapi-connection.c
+++ b/src/libexchangemapi/e-mapi-connection.c
@@ -172,8 +172,70 @@ struct _EMapiConnectionPrivate {
 
 	GHashTable *named_ids;		/* cache of named ids; key is a folder ID, value is a hash table
 					   of named_id to prop_id in that respective folder */
+
+	GHashTable *known_notifications;/* mapi_id_t * -> uint32_t for Unsubscribe call */
+	GThread *notification_thread;
+	EFlag *notification_flag;
+	enum MAPISTATUS	register_notification_result; /* MAPI_E_RESERVED if not called yet */
+	gint notification_poll_seconds; /* delay between polls, in seconds */
+};
+
+enum {
+	SERVER_NOTIFICATION,
+	LAST_SIGNAL
 };
 
+static guint signals[LAST_SIGNAL];
+
+static gboolean
+stop_notification (EMapiConnectionPrivate *priv,
+		   uint32_t conn_id,
+		   GCancellable *cancellable,
+		   GError **perror)
+{
+	enum MAPISTATUS ms;
+
+	e_return_val_mapi_error_if_fail (priv != NULL, MAPI_E_INVALID_PARAMETER, FALSE);
+	e_return_val_mapi_error_if_fail (priv->session != NULL, MAPI_E_INVALID_PARAMETER, FALSE);
+
+	LOCK ();
+
+	ms = Unsubscribe (priv->session, conn_id);
+	if (ms != MAPI_E_SUCCESS)
+		make_mapi_error (perror, "Unsubscribe", ms);
+
+	UNLOCK ();
+
+	return ms == MAPI_E_SUCCESS;
+}
+
+static void
+call_stop_notification (gpointer key,
+			gpointer value,
+			gpointer user_data)
+{
+	stop_notification (user_data, GPOINTER_TO_UINT (value), NULL, NULL);
+}
+
+static void
+stop_all_notifications (EMapiConnectionPrivate *priv)
+{
+	g_return_if_fail (priv != NULL);
+
+	if (!priv->notification_thread)
+		return;
+
+	LOCK ();
+	if (priv->session)
+		g_hash_table_foreach (priv->known_notifications, call_stop_notification, priv);
+	g_hash_table_remove_all (priv->known_notifications);
+	e_flag_set (priv->notification_flag);
+	UNLOCK ();
+
+	g_thread_join (priv->notification_thread);
+	priv->notification_thread = NULL;
+}
+
 /* should have session_lock locked already, when calling this function */
 static void
 disconnect (EMapiConnectionPrivate *priv)
@@ -228,7 +290,7 @@ ensure_public_store (EMapiConnectionPrivate *priv, GError **perror)
 }
 
 static void
-e_mapi_connection_finalize (GObject *object)
+e_mapi_connection_dispose (GObject *object)
 {
 	EMapiConnectionPrivate *priv;
 
@@ -237,6 +299,21 @@ e_mapi_connection_finalize (GObject *object)
 	priv = E_MAPI_CONNECTION (object)->priv;
 
 	if (priv) {
+		stop_all_notifications (priv);
+	}
+
+	if (G_OBJECT_CLASS (e_mapi_connection_parent_class)->dispose)
+		G_OBJECT_CLASS (e_mapi_connection_parent_class)->dispose (object);
+}
+
+static void
+e_mapi_connection_finalize (GObject *object)
+{
+	EMapiConnectionPrivate *priv;
+
+	priv = E_MAPI_CONNECTION (object)->priv;
+
+	if (priv) {
 		LOCK ();
 		disconnect (priv);
 		g_free (priv->profile);
@@ -252,6 +329,12 @@ e_mapi_connection_finalize (GObject *object)
 
 		e_mapi_utils_destroy_mapi_context (priv->mapi_ctx);
 		priv->mapi_ctx = NULL;
+
+		g_hash_table_destroy (priv->known_notifications);
+		priv->known_notifications = NULL;
+
+		e_flag_free (priv->notification_flag);
+		priv->notification_flag = NULL;
 	}
 
 	if (G_OBJECT_CLASS (e_mapi_connection_parent_class)->finalize)
@@ -266,7 +349,17 @@ e_mapi_connection_class_init (EMapiConnectionClass *klass)
 	g_type_class_add_private (klass, sizeof (EMapiConnectionPrivate));
 
 	object_class = G_OBJECT_CLASS (klass);
+	object_class->dispose = e_mapi_connection_dispose;
 	object_class->finalize = e_mapi_connection_finalize;
+
+	signals[SERVER_NOTIFICATION] = g_signal_new (
+		"server-notification",
+		G_OBJECT_CLASS_TYPE (object_class),
+		G_SIGNAL_RUN_FIRST | G_SIGNAL_DETAILED | G_SIGNAL_ACTION,
+		0, NULL, NULL,
+		g_cclosure_marshal_VOID__UINT_POINTER,
+		G_TYPE_NONE, 2,
+		G_TYPE_UINT, G_TYPE_POINTER);
 }
 
 static void
@@ -285,6 +378,18 @@ e_mapi_connection_init (EMapiConnection *conn)
 
 	conn->priv->named_ids = g_hash_table_new_full (g_int64_hash, g_int64_equal, g_free, (GDestroyNotify) g_hash_table_destroy);
 
+	conn->priv->known_notifications = g_hash_table_new_full (g_int64_hash, g_int64_equal, g_free, NULL);
+	conn->priv->notification_thread = NULL;
+	conn->priv->notification_flag = e_flag_new ();
+	conn->priv->register_notification_result = MAPI_E_RESERVED;
+	conn->priv->notification_poll_seconds = 60;
+
+	if (g_getenv ("MAPI_SERVER_POLL")) {
+		conn->priv->notification_poll_seconds = atoi (g_getenv ("MAPI_SERVER_POLL"));
+		if (conn->priv->notification_poll_seconds < 1)
+			conn->priv->notification_poll_seconds = 60;
+	}
+
 	register_connection (conn);
 }
 
@@ -316,7 +421,7 @@ unregister_connection (EMapiConnection *conn)
 	G_LOCK (known_connections);
 	if (!g_slist_find (known_connections, conn)) {
 		G_UNLOCK (known_connections);
-		g_return_if_reached ();
+		return;
 	}
 
 	known_connections = g_slist_remove (known_connections, conn);
@@ -5593,98 +5698,185 @@ e_mapi_connection_ex_to_smtp (EMapiConnection *conn,
 	return smtp_addr;
 }
 
-gboolean
-e_mapi_connection_events_init (EMapiConnection *conn,
-			       GCancellable *cancellable,
-			       GError **perror)
+static gint
+emit_server_notification_signal (uint16_t event_type, gpointer event_data, gpointer user_data)
 {
-	enum MAPISTATUS ms;
+	EMapiConnection *conn = user_data;
+	guint uint_event_type = event_type;
 
-	CHECK_CORRECT_CONN_AND_GET_PRIV (conn, FALSE);
-	e_return_val_mapi_error_if_fail (priv->session != NULL, MAPI_E_INVALID_PARAMETER, FALSE);
+	g_signal_emit (conn, signals[SERVER_NOTIFICATION], 0, uint_event_type, event_data);
 
-	LOCK ();
-	ms = RegisterNotification (priv->session, 0);
-	UNLOCK ();
+	return MAPI_E_SUCCESS;
+}
 
-	if (ms != MAPI_E_SUCCESS)
-		make_mapi_error (perror, "RegisterNotification", ms);
+static gpointer
+e_mapi_connection_notification_thread (gpointer user_data)
+{
+	EMapiConnection *conn = user_data;
+	EMapiConnectionPrivate *priv;
 
-	return ms == MAPI_E_SUCCESS;
+	g_return_val_if_fail (conn != NULL, NULL);
+	g_return_val_if_fail (conn->priv != NULL, NULL);
+	g_return_val_if_fail (conn->priv->session != NULL, NULL);
+
+	priv = conn->priv;
+
+	while (g_hash_table_size (priv->known_notifications) > 0) {
+		GTimeVal tv;
+
+		LOCK ();
+		/* this returns MAPI_E_INVALID_PARAMETER when there
+		   is no pending notification
+		*/
+		DispatchNotifications (priv->session);
+		UNLOCK ();
+
+		/* poll not so often */
+		g_get_current_time (&tv);
+		g_time_val_add (&tv, G_USEC_PER_SEC * priv->notification_poll_seconds);
+
+		e_flag_clear (priv->notification_flag);
+		e_flag_timed_wait (priv->notification_flag, &tv);
+	}
+
+	return NULL;
 }
 
+/* enables server notifications on a folder, or on a whole store, if obj_folder is NULL.
+   the event_mask can be 0 to obtain all notifications;
+   Pair function for this is e_mapi_connection_disable_notifications().
+   The notification is received to the caller with the "server-notification" signal.
+   Note that the signal is used for each notification, without distinction on the enable
+   object.
+*/
 gboolean
-e_mapi_connection_events_subscribe (EMapiConnection *conn,
-				    guint32 options,
-				    guint16 event_mask,
-				    guint32 *events_conn_id,
-				    mapi_notify_callback_t callback,
-				    gpointer data,
-				    GCancellable *cancellable,
-				    GError **perror)
+e_mapi_connection_enable_notifications (EMapiConnection *conn,
+					mapi_object_t *obj_folder,
+					uint32_t event_mask,
+					GCancellable *cancellable,
+					GError **perror)
 {
-	enum MAPISTATUS	ms = MAPI_E_CALL_FAILED;
-	gboolean use_store = ((options & MAPI_EVENTS_USE_STORE) ||
-			      (options & MAPI_EVENTS_USE_PF_STORE));
+	enum MAPISTATUS	ms;
+	mapi_id_t fid = 0;
+	uint32_t conn_id;
+	gint64 i64;
 
 	CHECK_CORRECT_CONN_AND_GET_PRIV (conn, FALSE);
 	e_return_val_mapi_error_if_fail (priv->session != NULL, MAPI_E_INVALID_PARAMETER, FALSE);
 
 	LOCK ();
 
-	if (options & MAPI_EVENTS_USE_STORE) {
-		ms = Subscribe (&priv->msg_store, events_conn_id, event_mask, use_store, (mapi_notify_callback_t) callback, data);
-	} else if (options & MAPI_EVENTS_USE_PF_STORE) {
-		if (!ensure_public_store (priv, perror)) {
-			UNLOCK ();
-			return FALSE;
-		}
+	if (event_mask == 0)
+		event_mask = fnevNewMail |
+			     fnevObjectCreated |
+			     fnevObjectDeleted |
+			     fnevObjectModified |
+			     fnevObjectMoved;
 
-		ms = Subscribe (&priv->public_store, events_conn_id, event_mask, use_store, (mapi_notify_callback_t) callback, data);
-	} else if (options & MAPI_EVENTS_FOLDER) {
-		/* TODO */
+	if (obj_folder)
+		fid = mapi_object_get_id (obj_folder);
+
+	i64 = (gint64) fid;
+	conn_id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->known_notifications, &i64));
+	if (conn_id) {
+		stop_notification (priv, conn_id, cancellable, perror);
+		g_hash_table_remove (priv->known_notifications, &i64);
 	}
 
-	UNLOCK ();
+	if (priv->register_notification_result == MAPI_E_RESERVED)
+		priv->register_notification_result = RegisterNotification (priv->session, fnevNewMail |
+			     fnevObjectCreated |
+			     fnevObjectDeleted |
+			     fnevObjectModified |
+			     fnevObjectMoved);
 
-	if (ms != MAPI_E_SUCCESS)
+	if (priv->register_notification_result != MAPI_E_SUCCESS) {
+		make_mapi_error (perror, "RegisterNotification", priv->register_notification_result);
+		UNLOCK ();
+
+		return FALSE;
+	}
+
+	conn_id = 0;
+	ms = Subscribe (obj_folder ? obj_folder : &priv->msg_store, &conn_id, event_mask, obj_folder == NULL, emit_server_notification_signal, conn);
+	if (ms == MAPI_E_SUCCESS) {
+		gint64 *pi64;
+
+		pi64 = g_new0 (gint64, 1);
+		*pi64 = i64;
+
+		g_hash_table_insert (priv->known_notifications, pi64, GUINT_TO_POINTER (conn_id));
+
+		if (priv->notification_thread) {
+			e_flag_set (priv->notification_flag);
+		} else {
+			priv->notification_thread = g_thread_create (e_mapi_connection_notification_thread, conn, TRUE, NULL);
+		}
+	} else {
 		make_mapi_error (perror, "Subscribe", ms);
+	}
 
-	return (ms == MAPI_E_SUCCESS);
+	UNLOCK ();
+
+	return ms == MAPI_E_SUCCESS;
 }
 
 gboolean
-e_mapi_connection_events_unsubscribe (EMapiConnection *conn, guint32 events_conn_id, GCancellable *cancellable, GError **perror)
+e_mapi_connection_disable_notifications	(EMapiConnection *conn,
+					 mapi_object_t *obj_folder,
+					 GCancellable *cancellable,
+					 GError **perror)
 {
-	enum MAPISTATUS	ms;
+	mapi_id_t fid = 0;
+	uint32_t conn_id;
+	gint64 i64;
 
 	CHECK_CORRECT_CONN_AND_GET_PRIV (conn, FALSE);
 	e_return_val_mapi_error_if_fail (priv->session != NULL, MAPI_E_INVALID_PARAMETER, FALSE);
 
 	LOCK ();
-	ms = Unsubscribe (priv->session, events_conn_id);
-	UNLOCK ();
 
-	if (ms != MAPI_E_SUCCESS)
-		make_mapi_error (perror, "Unsubscribe", ms);
+	if (!priv->notification_thread) {
+		/* no notifications started, just return */
+		UNLOCK ();
 
-	return (ms == MAPI_E_SUCCESS);
-}
+		return TRUE;
+	}
 
-/* Note : Blocking infinite loop. */
-gboolean
-e_mapi_connection_events_monitor (EMapiConnection *conn, struct mapi_notify_continue_callback_data *cb_data)
-{
-	enum MAPISTATUS	ms;
-	/* to have this used in the below macros */
-	GError **perror = NULL;
+	if (priv->register_notification_result != MAPI_E_SUCCESS) {
+		make_mapi_error (perror, "RegisterNotification", priv->register_notification_result);
+		UNLOCK ();
 
-	CHECK_CORRECT_CONN_AND_GET_PRIV (conn, FALSE);
-	e_return_val_mapi_error_if_fail (priv->session != NULL, MAPI_E_INVALID_PARAMETER, FALSE);
+		return FALSE;
+	}
 
-	ms = MonitorNotification (priv->session, NULL, cb_data);
+	if (obj_folder)
+		fid = mapi_object_get_id (obj_folder);
 
-	return ms == MAPI_E_SUCCESS;
+	i64 = (gint64) fid;
+	conn_id = GPOINTER_TO_UINT (g_hash_table_lookup (priv->known_notifications, &i64));
+	if (conn_id) {
+		gboolean stopped = stop_notification (priv, conn_id, cancellable, perror);
+		g_hash_table_remove (priv->known_notifications, &i64);
+
+		if (!stopped) {
+			UNLOCK ();
+			return FALSE;
+		}
+	} else {
+		make_mapi_error (perror, "e_mapi_connection_disable_notifications", MAPI_E_NOT_FOUND);
+		UNLOCK ();
+
+		return FALSE;
+	}
+
+	if (g_hash_table_size (priv->known_notifications) == 0) {
+		stop_all_notifications (priv);
+	}
+
+	UNLOCK ();
+
+	return TRUE;
 }
 
 /* profile related functions - begin */
diff --git a/src/libexchangemapi/e-mapi-connection.h b/src/libexchangemapi/e-mapi-connection.h
index eb7cfdd..07f40eb 100644
--- a/src/libexchangemapi/e-mapi-connection.h
+++ b/src/libexchangemapi/e-mapi-connection.h
@@ -526,25 +526,15 @@ gchar *			e_mapi_connection_ex_to_smtp		(EMapiConnection *conn,
 								 GError **perror);
 
 /* Push notifications APIs */
-typedef gboolean (*exchange_check_continue) (void);
 
-gboolean		e_mapi_connection_events_init		(EMapiConnection *conn,
-								 GCancellable *cancellable,
-								 GError **perror);
-gboolean		e_mapi_connection_events_monitor	(EMapiConnection *conn,
-								 struct mapi_notify_continue_callback_data *cb_data);
-
-gboolean		e_mapi_connection_events_subscribe	(EMapiConnection *conn,
-								 guint32 options,
-								 guint16 event_mask,
-								 guint32 *events_conn_id,
-								 mapi_notify_callback_t callback,
-								 gpointer data,
+gboolean		e_mapi_connection_enable_notifications	(EMapiConnection *conn,
+								 mapi_object_t *obj_folder,
+								 uint32_t event_mask,
 								 GCancellable *cancellable,
 								 GError **perror);
 
-gboolean		e_mapi_connection_events_unsubscribe	(EMapiConnection *conn,
-								 guint32 events_conn_id,
+gboolean		e_mapi_connection_disable_notifications	(EMapiConnection *conn,
+								 mapi_object_t *obj_folder,
 								 GCancellable *cancellable,
 								 GError **perror);
 



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