Support for NOTIFY, a first patch



This is obviously probably not going to work due to various subtile
bugs. But it should give people an idea on how NOTIFY support will be
put in place.

The idea is to keep using IDLE mode (I'm assuming all IMAP servers that
support NOTIFY are going to support IDLE, else neither of the
capabilities will be used) yet setting NOTIFY.

The NOTIFY SET will set a very basic setting that mimics normal IDLE but
adds getting notifications about folder creates, deletes and renames.

I will most likely add subscription changes too, as soon as we have
subscription support working very well and correct in Tinymail too (my
humble feeling about subscription support in Tinymail is that it's too
untested at this moment).

Those folder creates, deletes and renames will trigger the
TnyFolderObserver and TnyFolderStoreObserver infrastructure of Tinymail
to start kicking observers's asses. TnyGtkFolderStoreTreeModel already
correctly copes with that, by the way.

TnyGtkFolderStoreTreeModel will get the support for NOTIFY right
automatically for application developers. Application developers
depending on a TnyFolder themselves should, however, observe the
folder's parent to detect a remote removal, rename and child create of
and/or in the folder and/or in account instances.

IDLE already works fine with that TnyFolderMonitor thing. With NOTIFY
that will work exactly the same. Except that also the all_count of other
folders will be set as their changes get notified too now. That's just
the normal TnyFolderObserver stuff. TnyGtkFolderStoreTreeModel copes
with that already too.


-- 
Philip Van Hoof, freelance software developer
home: me at pvanhoof dot be 
gnome: pvanhoof at gnome dot org 
http://pvanhoof.be/blog
http://codeminded.be



Index: libtinymail-camel/camel-lite/camel/camel-store.c
===================================================================
--- libtinymail-camel/camel-lite/camel/camel-store.c	(revision 2946)
+++ libtinymail-camel/camel-lite/camel/camel-store.c	(working copy)
@@ -140,7 +140,8 @@
 	
 	camel_object_class->setv = store_setv;
 	camel_object_class->getv = store_getv;
-	
+
+	camel_object_class_add_event(camel_object_class, "folder_status", NULL);
 	camel_object_class_add_event(camel_object_class, "folder_opened", NULL);
 	camel_object_class_add_event(camel_object_class, "folder_created", NULL);
 	camel_object_class_add_event(camel_object_class, "folder_deleted", NULL);
@@ -149,6 +150,32 @@
 	camel_object_class_add_event(camel_object_class, "folder_unsubscribed", NULL);
 }
 
+CamelRenameInfo *camel_rename_info_new (void)
+{
+	return g_slice_new0 (CamelRenameInfo);
+}
+
+void camel_rename_info_free (CamelRenameInfo *info)
+{
+	if (info->old_base)
+		g_free (info->old_base);
+	if (info->new)
+		camel_folder_info_free (info->new);
+	g_slice_free (CamelRenameInfo, info);
+}
+
+CamelFolderStatus *camel_folder_status_new (void)
+{
+	return g_slice_new0 (CamelFolderStatus);
+}
+
+void camel_folder_status_free (CamelFolderStatus *r)
+{
+	if (r->full_name)
+		g_free (r->full_name);
+	g_slice_free (CamelFolderStatus, r);
+}
+
 static void
 camel_store_init (void *o)
 {
Index: libtinymail-camel/camel-lite/camel/camel-store.h
===================================================================
--- libtinymail-camel/camel-lite/camel/camel-store.h	(revision 2946)
+++ libtinymail-camel/camel-lite/camel/camel-store.h	(working copy)
@@ -39,6 +39,11 @@
 	CAMEL_STORE_ARG_FIRST = CAMEL_SERVICE_ARG_FIRST + 100
 };
 
+typedef struct {
+	gchar *full_name;
+	guint uidnext, messages;
+} CamelFolderStatus;
+
 typedef struct _CamelFolderInfo {
 	struct _CamelFolderInfo *next;
 	struct _CamelFolderInfo *parent;
@@ -291,6 +296,11 @@
 void camel_isubscribe_subscribe(CamelStore *store, const char *folder_name, CamelException *ex);
 void camel_isubscribe_unsubscribe(CamelStore *store, const char *folder_name, CamelException *ex);
 
+CamelFolderStatus *camel_folder_status_new (void);
+void camel_folder_status_free (CamelFolderStatus *fi);
+CamelRenameInfo *camel_rename_info_new (void);
+void camel_rename_info_free (CamelRenameInfo *info);
+
 G_END_DECLS
 
 #endif /* CAMEL_STORE_H */
Index: libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.c
===================================================================
--- libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.c	(revision 2946)
+++ libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.c	(working copy)
@@ -732,6 +732,7 @@
 	{ "QRESYNC",         	IMAP_CAPABILITY_QRESYNC },
 	{ "ENABLE",         	IMAP_CAPABILITY_ENABLE },
 	{ "ESEARCH",         	IMAP_CAPABILITY_ESEARCH },
+	{ "NOTIFY",         	IMAP_CAPABILITY_NOTIFY },
 
 	{ NULL, 0 }
 };
@@ -1050,7 +1051,7 @@
 		goto exception;
 	}
 
-	if (store->capabilities & IMAP_CAPABILITY_LOGINDISABLED ) { 
+	if (store->capabilities & IMAP_CAPABILITY_LOGINDISABLED) { 
 		clean_quit = TRUE;
 		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
 				"Failed to connect to IMAP server %s in secure mode: %s", 
@@ -1399,6 +1400,12 @@
 	return fi;
 }
 
+CamelFolderInfo *
+camel_imap_store_build_folder_info (CamelImapStore *imap_store, const char *folder_name)
+{
+	return imap_build_folder_info (imap_store, folder_name);
+}
+
 static void
 imap_folder_effectively_unsubscribed(CamelImapStore *imap_store, 
 				     const char *folder_name, CamelException *ex)
@@ -1806,6 +1813,14 @@
 				g_string_free (enable_line, TRUE);
 			}
 
+			if (store->capabilities & IMAP_CAPABILITY_NOTIFY) { 
+				response = camel_imap_command (store, NULL, ex, 
+					"NOTIFY SET"
+					"(selected MessageNew (uid) (all) FlagChange MessageExpunge) ");
+				if (response)
+					camel_imap_response_free_without_processing (store, response);
+			}
+
 	}
 
 	return TRUE;
Index: libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.h
===================================================================
--- libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.h	(revision 2946)
+++ libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.h	(working copy)
@@ -122,6 +122,7 @@
 #define IMAP_CAPABILITY_QRESYNC			(1 << 15)
 #define IMAP_CAPABILITY_ENABLE			(1 << 16)
 #define IMAP_CAPABILITY_ESEARCH			(1 << 17)
+#define IMAP_CAPABILITY_NOTIFY			(1 << 18)
 
 #define IMAP_PARAM_OVERRIDE_NAMESPACE		(1 << 0)
 #define IMAP_PARAM_CHECK_ALL			(1 << 1)
@@ -196,6 +197,8 @@
 void camel_imap_store_start_idle (CamelImapStore *store);
 void camel_imap_recon (CamelImapStore *store, CamelException *mex);
 
+CamelFolderInfo * camel_imap_store_build_folder_info (CamelImapStore *imap_store, const char *folder_name);
+
 G_END_DECLS
 
 #endif /* CAMEL_IMAP_STORE_H */
Index: libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-folder.c
===================================================================
--- libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-folder.c	(revision 2946)
+++ libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-folder.c	(working copy)
@@ -3685,6 +3685,76 @@
 static void
 consume_idle_line (CamelImapStore *store, CamelFolder *folder, char *resp, IdleResponse *idle_resp)
 {
+
+	if (store->capabilities & IMAP_CAPABILITY_NOTIFY) {
+
+		if (strchr (resp, '*') != NULL && (camel_strstrcase (resp, "LIST")))
+		{
+			char *oldname = camel_strstrcase (resp, "OLDNAME");
+			char *newname = strchr (resp, '"');
+
+			if (newname)
+				newname = strchr (newname, '"');
+			if (newname)
+				newname = strchr (newname, '"');
+			if (newname && strlen (newname) > 2)
+				newname++;
+
+			if (oldname && newname && strlen (oldname) > 13) {
+				/* This is a MailboxRename
+				Rename:
+				S: * LIST () "/" "NewMailbox" ("OLDNAME" ("OldMailbox"))
+				*/
+
+				char *ptr = strchr (oldname+11, '"');
+				oldname += 11;
+				if (ptr) {
+					CamelRenameInfo *ri = g_slice_new (CamelRenameInfo);
+					ri->new = camel_imap_store_build_folder_info (store, newname);
+					ri->old_base = g_strndup (oldname, ptr - oldname);
+					camel_object_trigger_event (CAMEL_OBJECT (store), "folder_renamed", ri);
+					camel_rename_info_free (ri);
+				}
+			} else if (!oldname) {
+				/* This is either a folder create or a folder delete 
+				Create:
+				S: * LIST () "/" "NewMailbox"
+				Delete:
+				S: * LIST (\NoSelect) "/" "INBOX.DeletedMailbox"
+				*/
+
+				CamelFolderInfo *fi = camel_imap_store_build_folder_info (store, newname);
+				camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", fi);
+				/* camel_object_trigger_event (CAMEL_OBJECT (store), "folder_deleted", fi); */
+				camel_folder_info_free (fi);
+			}
+		} 
+
+		if (strchr (resp, '*') != NULL && (camel_strstrcase (resp, "STATUS")))
+		{
+			/* * STATUS misc (UIDNEXT 999 MESSAGES 554)" */
+			char *ptr = strchr (resp, '(');
+			if (ptr) {
+				char *ptr_uidnext, *ptr_messages;
+
+				ptr_uidnext = camel_strstrcase (ptr, "UIDNEXT ");
+				ptr_messages = camel_strstrcase (ptr, "MESSAGES ");
+
+				/* TODO: we can handle HIGHESTMODSEQ here too 
+				 * TODO: no unread count? ESEARCH? */
+
+				if (ptr_uidnext && ptr_messages) {
+					CamelFolderStatus *info = camel_folder_status_new ();
+					info->full_name = g_strndup (resp+10, (ptr-1) - resp);
+					info->uidnext = strtoul (ptr_uidnext + 8, NULL, 10);
+					info->messages = strtoul (ptr_messages + 9, NULL, 10);
+					camel_object_trigger_event (CAMEL_OBJECT (store), "folder_status", info);
+					camel_folder_status_free (info);
+				}
+			}
+		}
+	}
+
 	if (strchr (resp, '*') != NULL && (camel_strstrcase (resp, "EXISTS") || 
 		camel_strstrcase (resp, "FETCH")|| camel_strstrcase (resp, "EXPUNGE") || 
 		camel_strstrcase (resp, "VANISHED") || camel_strstrcase (resp, "RECENT")))
@@ -3693,6 +3763,7 @@
 			idle_resp = idle_response_new (folder);
 		read_idle_response (folder, resp, idle_resp);
 	}
+
 	idle_debug ("(%d, ..) <- %s\n", 
 		strlen (resp), resp);
 	return;
@@ -4062,7 +4133,6 @@
 
 	if (store->capabilities & IMAP_CAPABILITY_IDLE)
 	{
-
 		g_static_rec_mutex_lock (store->idle_prefix_lock);
 		if (store->current_folder && !store->idle_prefix)
 		{
Index: libtinymail-camel/tny-camel-store-account.c
===================================================================
--- libtinymail-camel/tny-camel-store-account.c	(revision 2946)
+++ libtinymail-camel/tny-camel-store-account.c	(working copy)
@@ -33,6 +33,9 @@
 #include <tny-camel-store-account.h>
 #include <tny-folder-store-change.h>
 #include <tny-folder-store-observer.h>
+#include <tny-folder-change.h>
+#include <tny-folder-observer.h>
+
 #include <tny-simple-list.h>
 
 #include <tny-folder.h>
@@ -83,7 +86,37 @@
 	g_slice_free (NotFolObInIdleInfo, info);
 }
 
+
 static void
+notify_folder_observers_about (TnyFolder *self, TnyFolderChange *change)
+{
+	TnyCamelFolderPriv *priv = TNY_CAMEL_FOLDER_GET_PRIVATE (self);
+	TnyCamelAccountPriv *apriv = TNY_CAMEL_ACCOUNT_GET_PRIVATE (priv->account);
+	TnyIterator *iter;
+	GList *list;
+
+	g_static_rec_mutex_lock (priv->obs_lock);
+	if (!priv->obs) {
+		g_static_rec_mutex_unlock (priv->obs_lock);
+		return;
+	}
+	list = g_list_copy (priv->obs);
+	g_static_rec_mutex_unlock (priv->obs_lock);
+
+	while (list)
+	{
+		TnyFolderObserver *observer = TNY_FOLDER_OBSERVER (list->data);
+		tny_lockable_lock (apriv->session->priv->ui_lock);
+		tny_folder_observer_update (observer, change);
+		tny_lockable_unlock (apriv->session->priv->ui_lock);
+		list = g_list_next (list);
+	}
+	g_list_free (list);
+
+	return;
+}
+
+static void
 notify_folder_store_observers_about (TnyFolderStore *self, TnyFolderStoreChange *change)
 {
 	TnyCamelAccountPriv *apriv = NULL;
@@ -138,6 +171,15 @@
 	return FALSE;
 }
 
+static gboolean 
+notify_folder_observers_about_idle (gpointer user_data)
+{
+	NotFolObInIdleInfo *info = (NotFolObInIdleInfo *) user_data;
+	notify_folder_observers_about (TNY_FOLDER (info->self), 
+		TNY_FOLDER_CHANGE (info->change));
+	return FALSE;
+}
+
 static void
 notify_folder_store_observers_about_in_idle (TnyFolderStore *self, TnyFolderStoreChange *change)
 {
@@ -148,6 +190,17 @@
 		info, do_notify_in_idle_destroy);
 }
 
+
+static void
+notify_folder_observers_about_in_idle (TnyFolder *self, TnyFolderChange *change)
+{
+	NotFolObInIdleInfo *info = g_slice_new (NotFolObInIdleInfo);
+	info->self = g_object_ref (self);
+	info->change = g_object_ref (change);
+	g_idle_add_full (G_PRIORITY_HIGH, notify_folder_observers_about_idle,
+		info, do_notify_in_idle_destroy);
+}
+
 static gboolean
 connection_status_idle (gpointer data)
 {
@@ -374,6 +427,103 @@
 
 
 static void 
+folder_created (CamelStore *camel_store, CamelFolderInfo *new_folder, gpointer user_data)
+{
+	TnyCamelAccount *self = (TnyCamelAccount *) user_data;
+	TnyCamelAccountPriv *apriv = TNY_CAMEL_ACCOUNT_GET_PRIVATE (self);
+	gboolean was_new = FALSE;
+	TnyFolder *folder = tny_camel_store_account_factor_folder  
+		(TNY_CAMEL_STORE_ACCOUNT (self), new_folder->full_name, &was_new);
+
+	if (folder) {
+		TnyFolderStore *store = tny_folder_get_folder_store (folder);
+		TnyFolderStoreChange *change = tny_folder_store_change_new (store);
+		tny_folder_store_change_add_created_folder (change, folder);
+		notify_folder_store_observers_about_in_idle (store, change);
+		g_object_unref (change);
+		g_object_unref (folder);
+	}
+
+	return;
+}
+
+
+static void 
+folder_deleted (CamelStore *camel_store, CamelRenameInfo *info, gpointer user_data)
+{
+	TnyCamelAccount *self = (TnyCamelAccount *) user_data;
+	TnyCamelAccountPriv *apriv = TNY_CAMEL_ACCOUNT_GET_PRIVATE (self);
+	gboolean was_new = FALSE;
+	TnyFolder *folder = tny_camel_store_account_factor_folder  
+		(TNY_CAMEL_STORE_ACCOUNT (self), info->old_base, &was_new);
+
+	if (folder) {
+		TnyFolderStore *store = tny_folder_get_folder_store (folder);
+		TnyFolderStoreChange *change = tny_folder_store_change_new (store);
+		tny_folder_store_change_add_removed_folder (change, folder);
+		notify_folder_store_observers_about_in_idle (store, change);
+		g_object_unref (change);
+		g_object_unref (folder);
+	}
+
+	return;
+}
+
+
+static void 
+folder_renamed (CamelStore *camel_store, CamelRenameInfo *renamed_folder, gpointer user_data)
+{
+	TnyCamelAccount *self = (TnyCamelAccount *) user_data;
+	TnyCamelAccountPriv *apriv = TNY_CAMEL_ACCOUNT_GET_PRIVATE (self);
+	gboolean was_new = FALSE;
+	TnyFolder *folder = tny_camel_store_account_factor_folder  
+		(TNY_CAMEL_STORE_ACCOUNT (self), renamed_folder->old_base, &was_new);
+
+	if (folder) {
+		CamelFolderInfo *clone  = camel_folder_info_clone (renamed_folder->new);
+		TnyFolderChange *change = tny_folder_change_new (folder);
+
+		/* This will leak priv->iter of folder (we know, it's a small 
+		 * leak though)*/
+
+		_tny_camel_folder_set_folder_info (TNY_FOLDER_STORE (self), 
+			TNY_CAMEL_FOLDER (folder), clone);
+
+		tny_folder_change_set_rename (change, tny_folder_get_name (folder));
+
+		notify_folder_observers_about_in_idle (folder, change);
+		g_object_unref (change);
+		g_object_unref (folder);
+	}
+
+	return;
+}
+
+static void 
+folder_status (CamelStore *camel_store, CamelFolderStatus *status, gpointer user_data)
+{
+	TnyCamelAccount *self = (TnyCamelAccount *) user_data;
+	TnyCamelAccountPriv *apriv = TNY_CAMEL_ACCOUNT_GET_PRIVATE (self);
+	gboolean was_new = FALSE;
+	TnyFolder *folder = tny_camel_store_account_factor_folder  
+		(TNY_CAMEL_STORE_ACCOUNT (self), status->full_name, &was_new);
+
+	if (folder) {
+		TnyFolderChange *change = tny_folder_change_new (folder);
+		tny_folder_change_set_new_all_count (change, status->messages);
+		/* tny_folder_change_set_new_unread_count (change, status->messages); */
+		notify_folder_observers_about_in_idle (folder, change);
+		g_object_unref (change);
+		g_object_unref (folder);
+	}
+
+	return;
+}
+
+
+
+
+static void 
 tny_camel_store_account_prepare (TnyCamelAccount *self, gboolean recon_if, gboolean reservice)
 {
 	TnyCamelAccountPriv *apriv = TNY_CAMEL_ACCOUNT_GET_PRIVATE (self);
@@ -409,8 +559,30 @@
 			apriv->service->disconnecting = (con_op) disconnection;
 			apriv->service->reconnecter = (con_op) reconnecting;
 			apriv->service->reconnection = (con_op) reconnection;
-	
 
+			camel_object_hook_event (apriv->service, 
+				"folder_status", (CamelObjectEventHookFunc)folder_status, 
+				self);
+			camel_object_hook_event (apriv->service, 
+				"folder_renamed", (CamelObjectEventHookFunc)folder_renamed, 
+				self);
+			camel_object_hook_event (apriv->service, 
+				"folder_created", (CamelObjectEventHookFunc)folder_created, 
+				self);
+			camel_object_hook_event (apriv->service, 
+				"folder_deleted", (CamelObjectEventHookFunc)folder_deleted, 
+				self);
+
+/*			 TODO: First implement these in Camel too 
+
+			camel_object_hook_event (apriv->service, 
+				"folder_subscribed", (CamelObjectEventHookFunc)folder_subscribed, 
+				self);
+			camel_object_hook_event (apriv->service, 
+				"folder_unsubscribed", (CamelObjectEventHookFunc)folder_unsubscribed, 
+				self);
+*/
+
 		} else if (camel_exception_is_set (apriv->ex) && apriv->service)
 		{
 			g_warning ("Must cleanup service pointer\n");
@@ -1203,7 +1375,7 @@
 				/* TNY TODO: Temporary fix for empty root folders */
 				if (name && strlen(name) > 0)
 					tny_list_prepend (list, G_OBJECT (folder));
-				g_object_unref (G_OBJECT (folder));
+				g_object_unref (folder);
 			}
 		}
 		iter = iter->next;


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