Re: Support for NOTIFY, a first patch



This is an update on the recently proposed NOTIFY patch. It updates the
patch to cope with the massive amounts of changes that Matthew's merge
inflicted.

Note. Merging this patch wasn't hard, just two blocks of conflicts that
had to be solved by humans.


On Thu, 2007-11-15 at 01:00 +0100, Philip Van Hoof wrote:
> 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.
> 
> 
> _______________________________________________
> tinymail-devel-list mailing list
> tinymail-devel-list gnome org
> http://mail.gnome.org/mailman/listinfo/tinymail-devel-list
-- 
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 2950)
+++ libtinymail-camel/camel-lite/camel/camel-store.c	(working copy)
@@ -149,6 +149,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 2950)
+++ 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 2950)
+++ 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 }
 };
@@ -1390,6 +1391,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)
@@ -1789,6 +1796,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 2950)
+++ 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 2950)
+++ libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-folder.c	(working copy)
@@ -3685,6 +3685,79 @@
 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 (\NonExistent) "/" "INBOX.DeletedMailbox"
+				*/
+
+				CamelFolderInfo *fi = camel_imap_store_build_folder_info (store, newname);
+				if (camel_strstrcase (resp, "\NonExistent") == NULL)
+					camel_object_trigger_event (CAMEL_OBJECT (store), "folder_created", fi);
+				else
+					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")))
@@ -4062,7 +4135,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 2947)
+++ 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]