[Evolution-hackers] Quick 'n dirty POP summary support



Hi there,

This is a quick 'n dirty summary support for the Camel POP provider.

I'm not sure how this could be useful for Evolution, as Evolution
doesn't use support for summaries with POP accounts (it filters the
messages from the cache to a local folder of which its Camel provider
has summary support).

After this patch, you can use the POP Cache as only copy where the final
message is stored (if cached). It will create a summary file and
use/load that one for displaying the summary.

This has only been tested with tinymail, as I don't really feel like
adjusting Evolution not to do filtering but rather displaying POP
accounts like any other account (which is what would test this).

Note that this code is going to be slower than not supporting summaries
because I need the "TOP %s 1" of each message when refreshing the folder
info, whereas Evolution (if it uses the current filter technique) only
needs the list of UIDs (the LIST POP command).

This quick 'n dirty implementation is not tested very well. I still need
to check for silly memory leaks and stuff like that too.


As usual I send this stuff early so that the Evolution hackers have a
idea of what I'm cooking early on (I'm pro REAL development, rather than
pro bureaucratic development by only sending uber-cool finished patches:
that is not how software development works the best -- but anyway --).

Feel free to take a look at this, and let me know what you think of it.
I'm for example thinking about making it possible to disabling the
summary support (for example because Evolution, for example, doesn't
need it). It's going in tinymail's camel-lite anyhow .. but I would of
course prefer to have the delta of changes small.



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



Index: camel-pop3-folder.c
===================================================================
--- camel-pop3-folder.c	(revision 1300)
+++ camel-pop3-folder.c	(working copy)
@@ -54,8 +54,9 @@
 static void pop3_sync (CamelFolder *folder, gboolean expunge, CamelException *ex);
 static gint pop3_get_message_count (CamelFolder *folder);
 static GPtrArray *pop3_get_uids (CamelFolder *folder);
 static gboolean pop3_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set);
+static CamelMimeMessage *pop3_get_top (CamelFolder *folder, const char *uid, gboolean full, CamelException *ex);
 
 static void
 camel_pop3_folder_class_init (CamelPOP3FolderClass *camel_pop3_folder_class)
@@ -123,19 +124,44 @@
 camel_pop3_folder_new (CamelStore *parent, CamelException *ex)
 {
 	CamelFolder *folder;
+	CamelPOP3Store *p3store = (CamelPOP3Store*) parent;
+	gchar *summary_file;
 
 	d(printf("opening pop3 INBOX folder\n"));
 	
 	folder = CAMEL_FOLDER (camel_object_new (CAMEL_POP3_FOLDER_TYPE));
 	camel_folder_construct (folder, parent, "inbox", "inbox");
 	
+	summary_file = g_strdup_printf ("%s/summary.mmap", p3store->root);
+	folder->summary = camel_folder_summary_new (folder);
+	camel_folder_summary_set_build_content (folder->summary, TRUE);
+	camel_folder_summary_set_filename (folder->summary, summary_file);
+
+	if (camel_folder_summary_load (folder->summary) == -1) {
+		camel_folder_summary_clear (folder->summary);
+		camel_folder_summary_touch (folder->summary);
+		camel_folder_summary_save (folder->summary);
+		camel_folder_summary_load (folder->summary);
+	}
+	g_free (summary_file);
+
+
 	/* mt-ok, since we dont have the folder-lock for new() */
 	camel_folder_refresh_info (folder, ex);/* mt-ok */
 	if (camel_exception_is_set (ex)) {
 		camel_object_unref (CAMEL_OBJECT (folder));
 		folder = NULL;
 	}
+
+	if (!folder->summary) {
+		camel_object_unref (CAMEL_OBJECT (folder));
+		camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+				      _("Could not load summary for INBOX"));
+		return NULL;
+	}
 	
+	folder->folder_flags |= CAMEL_FOLDER_HAS_SUMMARY_CAPABILITY;
+
 	return folder;
 }
 
@@ -271,11 +297,31 @@
 	
 	camel_pop3_engine_command_free(pop3_store->engine, pcl);
 	
+	for (i=0;i<pop3_folder->uids->len;i++) 
+	{
+		CamelPOP3FolderInfo *fi = pop3_folder->uids->pdata[i];
+		CamelMessageInfoBase *mi;
+
+		mi = (CamelMessageInfoBase*) camel_folder_summary_uid (folder->summary, fi->uid);
+		if (!mi)
+		{
+			if (pop3_store->engine->capa & CAMEL_POP3_CAP_TOP) {
+			    CamelMimeMessage *msg = pop3_get_top (folder, fi->uid, FALSE, NULL);
+			    if (msg) camel_object_unref (CAMEL_OBJECT (msg));
+			} else {
+			    CamelMimeMessage *msg = pop3_get_message (folder, fi->uid, NULL);
+			    if (msg) camel_object_unref (CAMEL_OBJECT (msg));
+			}
+		} else
+			camel_message_info_free (mi);
+	}
+
 	if (pop3_store->engine->capa & CAMEL_POP3_CAP_UIDL) {
 		camel_pop3_engine_command_free(pop3_store->engine, pcu);
 	} else {
 		for (i=0;i<pop3_folder->uids->len;i++) {
 			CamelPOP3FolderInfo *fi = pop3_folder->uids->pdata[i];
+
 			if (fi->cmd) {
 				camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
 				fi->cmd = NULL;
@@ -386,6 +433,11 @@
 		if (pop3_store->cache && fi->uid)
 			camel_data_cache_remove(pop3_store->cache, "cache", fi->uid, NULL);
 		}
+
+
+		/* TNY TODO Shouldn't this remove message from memory? Oh well, adding it */
+		camel_object_unref (CAMEL_OBJECT (message));
+
 	     }
 
 	}
@@ -453,8 +505,10 @@
 	fi->stream = NULL;
 }
 
+
+
 static CamelMimeMessage *
-pop3_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
+pop3_get_message (CamelFolder *folder, const char *uid, CamelException *ex)
 {
 	CamelMimeMessage *message = NULL;
 	CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
@@ -464,7 +518,11 @@
 	char buffer[1];
 	int i, last;
 	CamelStream *stream = NULL;
+	CamelFolderSummary *summary = folder->summary;
+	CamelMessageInfoBase *mi;
 
+	/* TNY TODO: Implement partial message retrieval if full==TRUE */
+
 	fi = g_hash_table_lookup(pop3_folder->uids_uid, uid);
 	if (fi == NULL) {
 		camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
@@ -581,6 +639,17 @@
 		camel_object_unref((CamelObject *)message);
 		message = NULL;
 	}
+
+	mi = (CamelMessageInfoBase *) camel_folder_summary_uid (summary, uid);
+	if (mi) camel_message_info_free (mi);
+
+	mi = (CamelMessageInfoBase *) camel_folder_summary_info_new_from_message (summary, message);
+
+	mi->flags |= CAMEL_MESSAGE_INFO_UID_NEEDS_FREE;
+	mi->uid = g_strdup (fi->uid);
+
+	camel_folder_summary_add (summary, (CamelMessageInfo *)mi);
+
 done:
 	camel_object_unref((CamelObject *)stream);
 fail:
@@ -589,6 +658,139 @@
 	return message;
 }
 
+
+
+static CamelMimeMessage *
+pop3_get_top (CamelFolder *folder, const char *uid, gboolean full, CamelException *ex)
+{
+	CamelMimeMessage *message = NULL;
+	CamelPOP3Store *pop3_store = CAMEL_POP3_STORE (folder->parent_store);
+	CamelPOP3Folder *pop3_folder = (CamelPOP3Folder *)folder;
+	CamelPOP3Command *pcr;
+	CamelPOP3FolderInfo *fi;
+	char buffer[1];
+	int i, last;
+	CamelStream *stream = NULL, *old;
+	CamelFolderSummary *summary = folder->summary;
+	CamelMessageInfoBase *mi;
+
+	/* TNY TODO: Implement partial message retrieval if full==TRUE */
+
+	fi = g_hash_table_lookup(pop3_folder->uids_uid, uid);
+
+	if (fi == NULL) {
+		camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
+				      _("No message with UID %s"), uid);
+		return NULL;
+	}
+
+	old = fi->stream;
+
+	/* Sigh, most of the crap in this function is so that the cancel button
+	   returns the proper exception code.  Sigh. */
+
+	camel_operation_start_transient(NULL, _("Retrieving POP message %d"), fi->id);
+
+	/* If we have an oustanding retrieve message running, wait for that to complete
+	   & then retrieve from cache, otherwise, start a new one, and similar */
+
+	if (fi->cmd != NULL) {
+		while ((i = camel_pop3_engine_iterate(pop3_store->engine, fi->cmd)) > 0)
+			;
+
+		if (i == -1)
+			fi->err = errno;
+
+		/* getting error code? */
+		/*g_assert (fi->cmd->state == CAMEL_POP3_COMMAND_DATA);*/
+		camel_pop3_engine_command_free(pop3_store->engine, fi->cmd);
+		fi->cmd = NULL;
+
+		if (fi->err != 0) {
+			if (fi->err == EINTR)
+				camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
+			else
+				camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+						      _("Cannot get message %s: %s"),
+						      uid, g_strerror (fi->err));
+			goto fail;
+		}
+	}
+	
+	/* check to see if we have safely written flag set */
+	if (pop3_store->cache == NULL
+	    || (stream = camel_data_cache_get(pop3_store->cache, "cache", fi->uid, NULL)) == NULL
+	    || camel_stream_read(stream, buffer, 1) != 1
+	    || buffer[0] != '#') {
+			
+		stream = camel_stream_mem_new();
+		camel_object_ref (CAMEL_OBJECT (stream));
+
+		fi->stream = stream;
+		fi->err = EIO;
+
+		pcr = camel_pop3_engine_command_new(pop3_store->engine, CAMEL_POP3_COMMAND_MULTI, 
+			cmd_tocache, fi, "TOP %u 1\r\n", fi->id);
+
+		/* now wait for the first one to finish */
+		while ((i = camel_pop3_engine_iterate(pop3_store->engine, pcr)) > 0)
+			;
+		if (i == -1)
+			fi->err = errno;
+
+		camel_pop3_engine_command_free(pop3_store->engine, pcr);
+		camel_stream_reset(stream);
+
+		fi->stream = old;
+
+		/* Check to see we have safely written flag set */
+		if (fi->err != 0) {
+			if (fi->err == EINTR)
+				camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
+			else
+				camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+						      _("Cannot get message %s: %s"),
+						      uid, g_strerror (fi->err));
+			goto done;
+		}
+
+		if (camel_stream_read(stream, buffer, 1) != 1 || buffer[0] != '#') {
+			camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
+					     _("Cannot get message %s: %s"), uid, _("Unknown reason"));
+			goto done;
+		}
+	}
+
+	message = camel_mime_message_new ();
+	if (camel_data_wrapper_construct_from_stream((CamelDataWrapper *)message, stream) == -1) {
+		if (errno == EINTR)
+			camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("User canceled"));
+		else
+			camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
+					      _("Cannot get message %s: %s"),
+					      uid, g_strerror (errno));
+		camel_object_unref((CamelObject *)message);
+		message = NULL;
+	}
+
+	mi = (CamelMessageInfoBase *) camel_folder_summary_uid (summary, uid);
+	if (mi) camel_message_info_free (mi);
+
+	mi = (CamelMessageInfoBase *) camel_folder_summary_info_new_from_message (summary, message);
+
+	mi->flags |= CAMEL_MESSAGE_INFO_UID_NEEDS_FREE;
+	mi->uid = g_strdup (fi->uid);
+
+	camel_folder_summary_add (summary, (CamelMessageInfo *)mi);
+
+done:
+	camel_object_unref((CamelObject *)stream);
+fail:
+	camel_operation_end(NULL);
+
+	return message;
+}
+
 static gboolean
 pop3_set_message_flags (CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
 {
Index: camel-pop3-store.h
===================================================================
--- camel-pop3-store.h	(revision 1300)
+++ camel-pop3-store.h	(working copy)
@@ -51,6 +51,8 @@
 	struct _CamelDataCache *cache;
 	
 	guint delete_after;
+	gchar *root;
+
 } CamelPOP3Store;
 
 
Index: camel-pop3-store.c
===================================================================
--- camel-pop3-store.c	(revision 1300)
+++ camel-pop3-store.c	(working copy)
@@ -132,6 +132,8 @@
 		camel_object_unref((CamelObject *)pop3_store->engine);
 	if (pop3_store->cache)
 		camel_object_unref((CamelObject *)pop3_store->cache);
+	if (pop3_store->root)
+		g_free (pop3_store->root);
 }
 
 enum {
@@ -567,9 +569,10 @@
 		char *root;
 
 		root = camel_session_get_storage_path (session, service, ex);
+		store->root = root;
 		if (root) {
 			store->cache = camel_data_cache_new(root, 0, ex);
-			g_free(root);
+			/*g_free(root);*/
 			if (store->cache) {
 				/* Default cache expiry - 1 week or not visited in a day */
 				camel_data_cache_set_expire_age(store->cache, 60*60*24*7);


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