CONDSTORE capability support for tinymail's camel-lite



Well, it's not yet tested at all. I'm already posting this to have a
copy for myself when I will actually test this tomorrow.

This indeed needs some extensive testing. The removals are definitely
not yet right (CHANGEDSINCE doesn't give you the expunged ones as far as
I know). I'm probably going to checkout Polymer's code to figure out how
Dave did it ;-)

Again, it's not yet tested.
Index: libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.h
===================================================================
--- libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.h	(revision 1479)
+++ libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-store.h	(working copy)
@@ -99,6 +99,7 @@
 #define IMAP_CAPABILITY_XGWMOVE			(1 << 10)
 #define IMAP_CAPABILITY_LOGINDISABLED		(1 << 11)
 #define IMAP_CAPABILITY_CONDSTORE		(1 << 12)
+#define IMAP_CAPABILITY_IDLE			(1 << 13)
 
 #define IMAP_PARAM_OVERRIDE_NAMESPACE		(1 << 0)
 #define IMAP_PARAM_CHECK_ALL			(1 << 1)
Index: libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-folder.c
===================================================================
--- libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-folder.c	(revision 1479)
+++ libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-folder.c	(working copy)
@@ -92,6 +92,7 @@
 static void imap_finalize (CamelObject *object);
 static int imap_getv(CamelObject *object, CamelException *ex, CamelArgGetV *args);
 
+static void imap_rescan_condstore (CamelFolder *folder, int exists, const char *highestmodseq, CamelException *ex);
 static void imap_rescan (CamelFolder *folder, int exists, CamelException *ex);
 static void imap_refresh_info (CamelFolder *folder, CamelException *ex);
 static void imap_sync_online (CamelFolder *folder, CamelException *ex);
@@ -295,6 +296,45 @@
 	return folder;
 }
 
+
+static void 
+put_highestmodseq (CamelImapFolder *imap_folder, const char *highestmodseq)
+{
+	char *filename = g_strdup_printf ("%s/status", imap_folder->folder_dir);
+	FILE *file;
+	
+	file = fopen (filename, "w");
+	g_free (filename);
+
+	if (file != NULL)
+	{
+		fprintf (file, "%s", highestmodseq);
+		fclose (file);
+	}	
+}
+
+static char* 
+get_highestmodseq (CamelImapFolder *imap_folder)
+{
+	char *filename = g_strdup_printf ("%s/status", imap_folder->folder_dir);
+	/* max length in chars is that one, yes (the char values themselve 
+	   don't matter for this sizeof. It's just to reflect the RFC as-is) */
+	char *retval = NULL;
+	FILE *file;
+	
+	file = fopen (filename, "r");
+	g_free (filename);
+
+	if (file != NULL)
+	{
+		retval = g_malloc0 (sizeof ("18446744073709551615")); 
+		fscanf (file, "%s", &retval);
+		fclose (file);
+	}
+
+	return retval;
+}
+
 /* Called with the store's connect_lock locked */
 void
 camel_imap_folder_selected (CamelFolder *folder, CamelImapResponse *response,
@@ -307,10 +347,25 @@
 	guint32 perm_flags = 0;
 	GData *fetch_data;
 	int i, count;
-	char *resp;
+	char *resp, *highestmodseq = NULL;
+	CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
 	
 	count = camel_folder_summary_count (folder->summary);
 	
+
+/*
+      C: A142 SELECT INBOX (CONDSTORE)
+      S: * 172 EXISTS
+      S: * 1 RECENT
+      S: * OK [UNSEEN 12] Message 12 is first unseen
+      S: * OK [UIDVALIDITY 3857529045] UIDs valid
+      S: * OK [UIDNEXT 4392] Predicted next UID
+      S: * FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
+      S: * OK [PERMANENTFLAGS (\Deleted \Seen \*)] Limited
+      S: * OK [HIGHESTMODSEQ 715194045007]
+      S: A142 OK [READ-WRITE] SELECT completed, CONDSTORE is now enabled
+*/
+
 	for (i = 0; i < response->untagged->len; i++) {
 		resp = response->untagged->pdata[i] + 2;
 		if (!g_ascii_strncasecmp (resp, "FLAGS ", 6) && !perm_flags) {
@@ -323,6 +378,33 @@
 			 * even tho they do allow storing flags. *Sigh* So many fucking broken IMAP servers out there. */
 			if ((perm_flags = imap_parse_flag_list (&resp)) != 0)
 				folder->permanent_flags = perm_flags;
+		} else if (!g_ascii_strncasecmp (resp, "OK [HIGHESTMODSEQ ", sizeof ("OK [HIGHESTMODSEQ "))) {
+			char *marker;
+			unsigned int len;
+			resp += sizeof ("OK [HIGHESTMODSEQ ");
+
+			marker = strchr (resp, ']');
+
+			if (marker) {
+				char *phighestmodseq = NULL;
+
+				len = (unsigned int) (marker - resp);
+				highestmodseq = g_strndup (resp, len);
+
+				phighestmodseq = get_highestmodseq (imap_folder);
+				if (phighestmodseq && !strcmp (phighestmodseq, highestmodseq)) {
+					g_free (highestmodseq);
+					highestmodseq = NULL;
+				} else {
+					/* free highestmodseq later */
+					put_highestmodseq (imap_folder, (const char *) highestmodseq);
+				}
+
+				if (phighestmodseq)
+					g_free (phighestmodseq);
+			} else 
+				highestmodseq = NULL;
+
 		} else if (!g_ascii_strncasecmp (resp, "OK [UIDVALIDITY ", 16)) {
 			validity = strtoul (resp + 16, NULL, 10);
 		} else if (isdigit ((unsigned char)*resp)) {
@@ -366,11 +448,18 @@
 		return;
 	}
 	
+	if (highestmodseq != NULL && (store->capabilities & IMAP_CAPABILITY_CONDSTORE))
+	{
+		imap_rescan_condstore (folder, exists, highestmodseq, ex);
+	
+		g_free (highestmodseq); highestmodseq = NULL;
+		imap_folder->need_rescan = FALSE;
+	} else {
+
 	/* If we've lost messages, we have to rescan everything */
-	if (exists < count)
+	  if (exists < count)
 		imap_folder->need_rescan = TRUE;
-	else if (count != 0 && !imap_folder->need_rescan) {
-		CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+	  else if (count != 0 && !imap_folder->need_rescan) {
 		
 		/* Similarly, if the UID of the highest message we
 		 * know about has changed, then that indicates that
@@ -408,8 +497,9 @@
 		camel_message_info_free(info);
 		if (uid == 0 || uid != val)
 			imap_folder->need_rescan = TRUE;
+	  }
 	}
-	
+
 	/* Now rescan if we need to */
 	if (imap_folder->need_rescan) {
 		imap_rescan (folder, exists, ex);
@@ -618,6 +708,125 @@
 	}
 }
 
+static void 
+imap_rescan_condstore (CamelFolder *folder, int exists, const char *highestmodseq, CamelException *ex)
+{
+
+/*
+              C: s100 UID FETCH 1:* (FLAGS) (CHANGEDSINCE 12345)
+              S: * 1 FETCH (UID 4 MODSEQ (65402) FLAGS (\Seen))
+              S: * 2 FETCH (UID 6 MODSEQ (75403) FLAGS (\Deleted))
+              S: * 4 FETCH (UID 8 MODSEQ (29738) FLAGS ($NoJunk $AutoJunk $MDNSent))
+              S: s100 OK FETCH completed
+*/
+
+	CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder);
+	CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store);
+	struct {
+		char *uid;
+		guint32 flags;
+	} *new;
+	char *resp;
+	CamelImapResponseType type;
+	int i, seq, summary_len, summary_got;
+	CamelMessageInfo *info;
+	CamelImapMessageInfo *iinfo;
+	GArray *removed;
+	gboolean ok;
+	CamelFolderChangeInfo *changes = NULL;
+
+	imap_folder->need_rescan = FALSE;
+	
+	summary_len = camel_folder_summary_count (folder->summary);
+	if (summary_len == 0) {
+		if (exists)
+			camel_imap_folder_changed (folder, exists, NULL, ex);
+		return;
+	}
+
+	camel_operation_start (NULL, _("Scanning for changed messages in %s"), folder->name);
+	info = camel_folder_summary_index (folder->summary, summary_len - 1);
+	ok = camel_imap_command_start (store, folder, ex,
+				       "UID FETCH 1:* (FLAGS) (CHANGEDSINCE %s)",
+				       highestmodseq);
+	if (!ok) {
+		imap_folder->need_rescan = TRUE;
+		camel_operation_end (NULL);
+		return;
+	}
+	
+	summary_got = 0;
+	while ((type = camel_imap_command_response (store, &resp, ex)) == CAMEL_IMAP_RESPONSE_UNTAGGED) 
+	{
+		GData *data;
+		char *uid;
+		guint32 flags;
+		
+		data = parse_fetch_response (imap_folder, resp);
+		g_free (resp);
+
+		if (!data)
+			continue;
+		
+		seq = GPOINTER_TO_INT (g_datalist_get_data (&data, "SEQUENCE"));
+		uid = g_datalist_get_data (&data, "UID");
+		flags = GPOINTER_TO_UINT (g_datalist_get_data (&data, "FLAGS"));
+		
+		if (!uid || !seq || seq > summary_len) {
+			g_datalist_clear (&data);
+			continue;
+		}
+		
+		info = camel_folder_summary_uid (folder->summary, uid);
+		iinfo = (CamelImapMessageInfo *) info;
+		if (info) {
+
+			if (flags != iinfo->server_flags) {
+				guint32 server_set, server_cleared;
+			
+				server_set = flags & ~iinfo->server_flags;
+				server_cleared = iinfo->server_flags & ~flags;
+
+				iinfo->info.flags = (iinfo->info.flags | server_set) & ~server_cleared;
+				iinfo->server_flags = flags;
+
+				if (changes == NULL)
+					changes = camel_folder_change_info_new();
+				camel_folder_change_info_change_uid(changes, g_strdup (uid));
+				flags_to_label(folder, (CamelImapMessageInfo *)info);
+			}
+
+			camel_message_info_free (info);
+		} else {
+			camel_message_info_free(info);
+			g_array_append_val (removed, seq); /* TODO: check the value of seq! */
+			summary_len--;
+		}
+
+		camel_operation_progress (NULL, ++summary_got , summary_len);
+		g_datalist_clear (&data);
+	}
+	
+	camel_operation_end (NULL);
+
+	if (type == CAMEL_IMAP_RESPONSE_ERROR)
+		return;
+
+	/* Free the final tagged response */
+	g_free (resp);
+
+	if (changes) {
+		camel_object_trigger_event(CAMEL_OBJECT (folder), "folder_changed", changes);
+		camel_folder_change_info_free(changes);
+	}
+	
+	
+	/* And finally update the summary. */
+	camel_imap_folder_changed (folder, exists, removed, ex);
+	g_array_free (removed, TRUE);
+}
+
+
 /* Called with the store's connect_lock locked */
 static void
 imap_rescan (CamelFolder *folder, int exists, CamelException *ex)
@@ -654,6 +863,7 @@
 				       camel_message_info_uid (info));
 	camel_message_info_free(info);
 	if (!ok) {
+		imap_folder->need_rescan = TRUE;
 		camel_operation_end (NULL);
 		return;
 	}
@@ -2640,6 +2850,7 @@
 					  flags = GPOINTER_TO_INT (g_datalist_get_data (&data, "FLAGS"));
 					  if (flags) 
 					  {
+						clear_normal_flags (mi);
 						mi->server_flags = flags;
 						mi->info.flags |= flags;
 						flags_to_label(folder, mi);
Index: libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-command.c
===================================================================
--- libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-command.c	(revision 1479)
+++ libtinymail-camel/camel-lite/camel/providers/imap/camel-imap-command.c	(working copy)
@@ -103,7 +103,10 @@
 		if (store->current_folder && CAMEL_IS_OBJECT (store->current_folder))
 			camel_object_unref(store->current_folder);
 		store->current_folder = folder;
-		cmd = imap_command_strdup_printf (store, "SELECT %F", folder->full_name);
+		if (store->capabilities & IMAP_CAPABILITY_CONDSTORE)
+			cmd = imap_command_strdup_printf (store, "SELECT %F (CONDSTORE)", folder->full_name);
+		else
+			cmd = imap_command_strdup_printf (store, "SELECT %F", folder->full_name);
 	}
 	
 	if (!imap_command_start (store, folder, cmd, ex)) {


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