From eb7438182ab5fd969aac96795f67034e21937e35 Mon Sep 17 00:00:00 2001 From: Dan Winship Date: Tue, 1 May 2001 14:51:36 +0000 Subject: Support the IMAP UIDPLUS extension (RFC 2359), which lets you resync after disconnected operation more efficiently, but also makes it possible to do appends and moves/copies more efficiently now. * providers/imap/camel-imap-folder.c (imap_append_message): If the server supports UIDPLUS, grab the APPENDUID response and cache the message into the folder's message cache. (imap_copy_messages_to): Likewise, for COPYUID, copy any message parts we have cached between the source and destination folder caches. (imap_get_message): If the entire message is already in the cache, just return that rather than building it from parts. (imap_update_summary): Fetch just the "UID FLAGS RFC822.SIZE" of the new messages first, then only fetch the headers for messages where we don't already have the headers cached. * providers/imap/camel-imap-message-cache.c: Add gtk-doc comments. (cache_put): Fix refcounting stuff here. (camel_imap_message_cache_insert_stream, camel_imap_message_cache_insert_wrapper): New. (camel_imap_message_cache_get): Fix a bug here so the memory caching actually works. (camel_imap_message_cache_copy): New routine, used by imap_copy_messages_to. * providers/imap/camel-imap-utils.c (imap_uid_set_to_array): Inverse operation of imap_uid_array_to_set. Used to parse COPYUID response. svn path=/trunk/; revision=9635 --- camel/ChangeLog | 32 ++++ camel/providers/imap/camel-imap-folder.c | 173 +++++++++++++----- camel/providers/imap/camel-imap-message-cache.c | 228 +++++++++++++++++++++--- camel/providers/imap/camel-imap-message-cache.h | 14 ++ camel/providers/imap/camel-imap-utils.c | 93 +++++++++- camel/providers/imap/camel-imap-utils.h | 2 + 6 files changed, 469 insertions(+), 73 deletions(-) (limited to 'camel') diff --git a/camel/ChangeLog b/camel/ChangeLog index 30b479a59f..9c367d7cf9 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -1,3 +1,35 @@ +2001-05-01 Dan Winship + + Support the IMAP UIDPLUS extension (RFC 2359), which lets you + resync after disconnected operation more efficiently, but also + makes it possible to do appends and moves/copies more efficiently + now. + + * providers/imap/camel-imap-folder.c (imap_append_message): If the + server supports UIDPLUS, grab the APPENDUID response and cache the + message into the folder's message cache. + (imap_copy_messages_to): Likewise, for COPYUID, copy any message + parts we have cached between the source and destination folder + caches. + (imap_get_message): If the entire message is already in the cache, + just return that rather than building it from parts. + (imap_update_summary): Fetch just the "UID FLAGS RFC822.SIZE" of + the new messages first, then only fetch the headers for messages + where we don't already have the headers cached. + + * providers/imap/camel-imap-message-cache.c: Add gtk-doc comments. + (cache_put): Fix refcounting stuff here. + (camel_imap_message_cache_insert_stream, + camel_imap_message_cache_insert_wrapper): New. + (camel_imap_message_cache_get): Fix a bug here so the memory + caching actually works. + (camel_imap_message_cache_copy): New routine, used by + imap_copy_messages_to. + + * providers/imap/camel-imap-utils.c (imap_uid_set_to_array): + Inverse operation of imap_uid_array_to_set. Used to parse COPYUID + response. + 2001-04-30 Dan Winship * providers/imap/camel-imap-utils.c (imap_uid_array_to_set): diff --git a/camel/providers/imap/camel-imap-folder.c b/camel/providers/imap/camel-imap-folder.c index 89ac95fea5..ebfb34a0c0 100644 --- a/camel/providers/imap/camel-imap-folder.c +++ b/camel/providers/imap/camel-imap-folder.c @@ -642,8 +642,8 @@ imap_append_message (CamelFolder *folder, CamelMimeMessage *message, CamelMimeFilter *crlf_filter; CamelStreamFilter *streamfilter; GByteArray *ba; - char *flagstr, *result; - + char *flagstr, *result, *uid; + if (!camel_imap_store_check_online (store, ex)) return; @@ -695,9 +695,81 @@ imap_append_message (CamelFolder *folder, CamelMimeMessage *message, CAMEL_IMAP_STORE_UNLOCK(store, command_lock); if (!response) return; + + if (store->capabilities & IMAP_CAPABILITY_UIDPLUS) { + uid = strstrcase (response->status, "[APPENDUID "); + if (uid) + uid = strchr (uid + 11, ' '); + if (uid) + uid = g_strndup (uid + 1, strcspn (uid + 1, "]")); + if (uid) { + /* Make sure it's a number */ + if (strtoul (uid, &result, 10) != 0 && !*result) { + /* OK. Cache the data. */ + camel_imap_message_cache_insert_wrapper ( + CAMEL_IMAP_FOLDER (folder)->cache, + uid, "", CAMEL_DATA_WRAPPER (message)); + } + g_free (uid); + } + } + camel_imap_response_free (response); } +static void +handle_copyuid (CamelImapResponse *response, CamelFolder *source, + CamelFolder *destination) +{ + CamelImapMessageCache *scache = CAMEL_IMAP_FOLDER (source)->cache; + CamelImapMessageCache *dcache = CAMEL_IMAP_FOLDER (destination)->cache; + char *validity, *srcset, *destset; + GPtrArray *src, *dest; + int i; + + validity = strstrcase (response->status, "[COPYUID "); + if (!validity) + return; + validity += 9; + if (strtoul (validity, NULL, 10) != + CAMEL_IMAP_SUMMARY (destination->summary)->validity) + return; + + srcset = strchr (validity, ' '); + if (!srcset++) + goto lose; + destset = strchr (srcset, ' '); + if (!destset++) + goto lose; + + src = imap_uid_set_to_array (source->summary, srcset); + dest = imap_uid_set_to_array (destination->summary, destset); + + if (src && dest && src->len == dest->len) { + /* We don't have to worry about deadlocking on the + * cache locks here, because we've got the store's + * command lock too, so no one else could be here. + */ + CAMEL_IMAP_FOLDER_LOCK (source, cache_lock); + CAMEL_IMAP_FOLDER_LOCK (destination, cache_lock); + for (i = 0; i < src->len; i++) { + camel_imap_message_cache_copy (scache, src->pdata[i], + dcache, dest->pdata[i]); + } + CAMEL_IMAP_FOLDER_UNLOCK (source, cache_lock); + CAMEL_IMAP_FOLDER_UNLOCK (destination, cache_lock); + + imap_uid_array_free (src); + imap_uid_array_free (dest); + return; + } + + imap_uid_array_free (src); + imap_uid_array_free (dest); + lose: + g_warning ("Bad COPYUID response from server"); +} + static void imap_copy_messages_to (CamelFolder *source, GPtrArray *uids, CamelFolder *destination, CamelException *ex) @@ -719,7 +791,9 @@ imap_copy_messages_to (CamelFolder *source, GPtrArray *uids, set = imap_uid_array_to_set (source->summary, uids); response = camel_imap_command (store, source, ex, "UID COPY %s %S", set, destination->full_name); - + if (response && (store->capabilities & IMAP_CAPABILITY_UIDPLUS)) + handle_copyuid (response, source, destination); + camel_imap_response_free (response); g_free (set); CAMEL_IMAP_STORE_UNLOCK(store, command_lock); @@ -911,18 +985,21 @@ imap_get_message (CamelFolder *folder, const char *uid, CamelException *ex) CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); CamelMessageInfo *mi; CamelMimeMessage *msg; - CamelStream *stream; + CamelStream *stream = NULL; mi = camel_folder_summary_uid (folder->summary, uid); g_return_val_if_fail (mi != NULL, NULL); /* If the message is small, or the server doesn't support - * IMAP4rev1, fetch it in one piece. + * IMAP4rev1, or we already have the whole thing cached, + * fetch it in one piece. */ if (mi->size < IMAP_SMALL_BODY_SIZE || - store->server_level < IMAP_LEVEL_IMAP4REV1) { + store->server_level < IMAP_LEVEL_IMAP4REV1 || + (stream = camel_imap_folder_fetch_data (imap_folder, uid, "", TRUE, NULL))) { camel_folder_summary_info_free (folder->summary, mi); - stream = camel_imap_folder_fetch_data (imap_folder, uid, "", FALSE, ex); + if (!stream) + stream = camel_imap_folder_fetch_data (imap_folder, uid, "", FALSE, ex); if (!stream) return NULL; msg = camel_mime_message_new (); @@ -986,15 +1063,6 @@ imap_get_message (CamelFolder *folder, const char *uid, CamelException *ex) return msg; } -static const char * -imap_protocol_get_summary_specifier (CamelImapStore *store) -{ - if (store->server_level >= IMAP_LEVEL_IMAP4REV1) - return "UID FLAGS RFC822.SIZE BODY.PEEK[HEADER]"; - else - return "UID FLAGS RFC822.SIZE BODY.PEEK[0]"; -} - static void imap_update_summary (CamelFolder *folder, CamelFolderChangeInfo *changes, @@ -1003,64 +1071,83 @@ imap_update_summary (CamelFolder *folder, CamelImapFolder *imap_folder = CAMEL_IMAP_FOLDER (folder); CamelImapStore *store = CAMEL_IMAP_STORE (folder->parent_store); CamelImapResponse *response; - GPtrArray *headers, *messages; + GPtrArray *lines, *messages; const char *summary_specifier; - char *p; + char *p, *uid; int i, seq, first, exists = 0; CamelMimeMessage *msg; CamelMessageInfo *mi; GData *fetch_data; + CamelStream *stream; first = camel_folder_summary_count (folder->summary) + 1; summary_specifier = imap_protocol_get_summary_specifier (store); /* We already have the command lock */ - response = camel_imap_command (store, folder, ex, "FETCH %d:* (%s)", - first, summary_specifier); + response = camel_imap_command (store, folder, ex, "FETCH %d:* (UID FLAGS RFC822.SIZE)", first); if (!response) return; + /* Walk through the responses, looking for UIDs, and make sure + * we have those headers cached. + */ messages = g_ptr_array_new (); - headers = response->untagged; - for (i = 0; i < headers->len; i++) { - p = headers->pdata[i]; - if (*p++ != '*' || *p++ != ' ') + lines = response->untagged; + for (i = 0; i < lines->len; i++) { + p = lines->pdata[i]; + if (*p++ != '*' || *p++ != ' ') { + g_ptr_array_remove_index_fast (lines, i--); continue; + } seq = strtoul (p, &p, 10); if (!g_strcasecmp (p, " EXISTS")) { exists = seq; + g_ptr_array_remove_index_fast (lines, i--); continue; } - if (!seq || seq < first) + if (!seq || seq < first || g_strncasecmp (p, " FETCH (", 8) != 0) { + g_ptr_array_remove_index_fast (lines, i--); continue; - if (g_strncasecmp (p, " FETCH (", 8) != 0) - continue; - p += 7; + } if (seq - first >= messages->len) g_ptr_array_set_size (messages, seq - first + 1); - mi = messages->pdata[seq - first]; - fetch_data = parse_fetch_response (imap_folder, p); - if (!mi) { - CamelStream *stream; - - if (!g_datalist_get_data (&fetch_data, "BODY_PART_DATA")) { - g_datalist_clear (&fetch_data); - p = headers->pdata[i]; - g_ptr_array_remove_index (headers, i--); - g_ptr_array_add (headers, p); - continue; + fetch_data = parse_fetch_response (imap_folder, p + 7); + uid = g_datalist_get_data (&fetch_data, "UID"); + if (uid) { + stream = camel_imap_folder_fetch_data ( + imap_folder, uid, + store->server_level >= IMAP_LEVEL_IMAP4REV1 ? + "HEADER" : "0", FALSE, ex); + if (!stream) { + camel_imap_response_free_without_processing (response); + /* XXX messages */ + return; } msg = camel_mime_message_new (); - stream = g_datalist_get_data (&fetch_data, "BODY_PART_STREAM"); camel_data_wrapper_construct_from_stream (CAMEL_DATA_WRAPPER (msg), stream); + camel_object_unref (CAMEL_OBJECT (stream)); mi = camel_folder_summary_info_new_from_message (folder->summary, msg); camel_object_unref (CAMEL_OBJECT (msg)); messages->pdata[seq - first] = mi; } + g_datalist_clear (&fetch_data); + } + + /* Now go back through and create summary items */ + lines = response->untagged; + for (i = 0; i < lines->len; i++) { + p = lines->pdata[i]; + seq = strtoul (p + 2, &p, 10); + p = strchr (p, '('); + + mi = messages->pdata[seq - first]; + if (!mi) /* ? */ + continue; + fetch_data = parse_fetch_response (imap_folder, p); if (g_datalist_get_data (&fetch_data, "UID")) camel_message_info_set_uid (mi, g_strdup (g_datalist_get_data (&fetch_data, "UID"))); @@ -1191,10 +1278,10 @@ camel_imap_folder_fetch_data (CamelImapFolder *imap_folder, const char *uid, camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, _("Could not find message body in FETCH " "response.")); - } else + } else { camel_object_ref (CAMEL_OBJECT (stream)); - - g_datalist_clear (&fetch_data); + g_datalist_clear (&fetch_data); + } return stream; } diff --git a/camel/providers/imap/camel-imap-message-cache.c b/camel/providers/imap/camel-imap-message-cache.c index cd96409515..45f8d6db51 100644 --- a/camel/providers/imap/camel-imap-message-cache.c +++ b/camel/providers/imap/camel-imap-message-cache.c @@ -33,6 +33,7 @@ #include #include "camel-imap-message-cache.h" +#include "camel-data-wrapper.h" #include "camel-exception.h" #include "camel-stream-fs.h" @@ -92,18 +93,27 @@ cache_put (CamelImapMessageCache *cache, const char *uid, const char *key, { char *hash_key; GPtrArray *subparts; - gpointer old_key, old_value; + gpointer okey, ostream; - hash_key = g_strdup (key); subparts = g_hash_table_lookup (cache->parts, uid); if (!subparts) { subparts = g_ptr_array_new (); g_hash_table_insert (cache->parts, g_strdup (uid), subparts); - } else if (g_hash_table_lookup_extended (cache->parts, hash_key, - &old_key, &old_value)) - g_ptr_array_remove (subparts, old_key); + } + + if (g_hash_table_lookup_extended (cache->parts, key, &okey, &ostream)) { + if (ostream) { + camel_object_unhook_event (ostream, "finalize", + stream_finalize, cache); + g_hash_table_remove (cache->cached, ostream); + camel_object_unref (ostream); + } + hash_key = okey; + } else { + hash_key = g_strdup (key); + g_ptr_array_add (subparts, hash_key); + } - g_ptr_array_add (subparts, hash_key); g_hash_table_insert (cache->parts, hash_key, stream); g_hash_table_insert (cache->cached, stream, hash_key); @@ -113,6 +123,16 @@ cache_put (CamelImapMessageCache *cache, const char *uid, const char *key, } } +/** + * camel_imap_message_cache_new: + * @path: directory to use for storage + * @summary: CamelFolderSummary for the folder we are caching + * @ex: a CamelException + * + * Return value: a new CamelImapMessageCache object using @path for + * storage. If cache files already exist in @path, then any that do not + * correspond to messages in @summary will be deleted. + **/ CamelImapMessageCache * camel_imap_message_cache_new (const char *path, CamelFolderSummary *summary, CamelException *ex) @@ -187,44 +207,145 @@ stream_finalize (CamelObject *stream, gpointer event_data, gpointer user_data) g_hash_table_insert (cache->parts, key, NULL); } -CamelStream * -camel_imap_message_cache_insert (CamelImapMessageCache *cache, const char *uid, - const char *part_spec, const char *data, - int len) + +static CamelStream * +insert_setup (CamelImapMessageCache *cache, const char *uid, + const char *part_spec, char **path, char **key) { - char *path, *key; - int fd, status; CamelStream *stream; + int fd; - path = g_strdup_printf ("%s/%s.%s", cache->path, uid, part_spec); - key = strrchr (path, '/') + 1; - stream = g_hash_table_lookup (cache->parts, key); + *path = g_strdup_printf ("%s/%s.%s", cache->path, uid, part_spec); + *key = strrchr (*path, '/') + 1; + stream = g_hash_table_lookup (cache->parts, *key); if (stream) camel_object_unref (CAMEL_OBJECT (stream)); - fd = open (path, O_RDWR | O_CREAT | O_TRUNC, 0600); + fd = open (*path, O_RDWR | O_CREAT | O_TRUNC, 0600); if (fd == -1) { - g_free (path); + g_free (*path); return NULL; } - stream = camel_stream_fs_new_with_fd (fd); - status = camel_stream_write (stream, data, len); - camel_stream_reset (stream); + return camel_stream_fs_new_with_fd (fd); +} - if (status == -1) { - unlink (path); - g_free (path); - camel_object_unref (CAMEL_OBJECT (stream)); - return NULL; - } +static CamelStream * +insert_abort (char *path, CamelStream *stream) +{ + unlink (path); + g_free (path); + camel_object_unref (CAMEL_OBJECT (stream)); + return NULL; +} +static CamelStream * +insert_finish (CamelImapMessageCache *cache, const char *uid, + char *path, char *key, CamelStream *stream) +{ + camel_stream_reset (stream); cache_put (cache, uid, key, stream); + printf ("caching %s\n", path); g_free (path); return stream; } +/** + * camel_imap_message_cache_insert: + * @cache: the cache + * @uid: UID of the message data to cache + * @part_spec: the IMAP part_spec of the data + * @data: the data + * @len: length of @data + * + * Caches the provided data into @cache. + * + * Return value: a CamelStream containing the cached data, which the + * caller must unref. + **/ +CamelStream * +camel_imap_message_cache_insert (CamelImapMessageCache *cache, const char *uid, + const char *part_spec, const char *data, + int len) +{ + char *path, *key; + CamelStream *stream; + + stream = insert_setup (cache, uid, part_spec, &path, &key); + if (!stream) + return NULL; + if (camel_stream_write (stream, data, len) == -1) + return insert_abort (path, stream); + return insert_finish (cache, uid, path, key, stream); +} + +/** + * camel_imap_message_cache_insert_stream: + * @cache: the cache + * @uid: UID of the message data to cache + * @part_spec: the IMAP part_spec of the data + * @data_stream: the stream to cache + * + * Caches the provided data into @cache. + **/ +void +camel_imap_message_cache_insert_stream (CamelImapMessageCache *cache, + const char *uid, const char *part_spec, + CamelStream *data_stream) +{ + char *path, *key; + CamelStream *stream; + + stream = insert_setup (cache, uid, part_spec, &path, &key); + if (!stream) + return; + if (camel_stream_write_to_stream (data_stream, stream) == -1) + insert_abort (path, stream); + else { + insert_finish (cache, uid, path, key, stream); + camel_object_unref (CAMEL_OBJECT (stream)); + } +} + +/** + * camel_imap_message_cache_insert_wrapper: + * @cache: the cache + * @uid: UID of the message data to cache + * @part_spec: the IMAP part_spec of the data + * @wrapper: the wrapper to cache + * + * Caches the provided data into @cache. + **/ +void +camel_imap_message_cache_insert_wrapper (CamelImapMessageCache *cache, + const char *uid, const char *part_spec, + CamelDataWrapper *wrapper) +{ + char *path, *key; + CamelStream *stream; + + stream = insert_setup (cache, uid, part_spec, &path, &key); + if (!stream) + return; + if (camel_data_wrapper_write_to_stream (wrapper, stream) == -1) + insert_abort (path, stream); + else { + insert_finish (cache, uid, path, key, stream); + camel_object_unref (CAMEL_OBJECT (stream)); + } +} + + +/** + * camel_imap_message_cache_get: + * @cache: the cache + * @uid: the UID of the data to get + * @part_spec: the part_spec of the data to get + * + * Return value: a CamelStream containing the cached data (which the + * caller must unref), or %NULL if that data is not cached. + **/ CamelStream * camel_imap_message_cache_get (CamelImapMessageCache *cache, const char *uid, const char *part_spec) @@ -233,7 +354,7 @@ camel_imap_message_cache_get (CamelImapMessageCache *cache, const char *uid, char *path, *key; path = g_strdup_printf ("%s/%s.%s", cache->path, uid, part_spec); - key = strrchr (path, '/'); + key = strrchr (path, '/') + 1; stream = g_hash_table_lookup (cache->parts, key); if (stream) { camel_object_ref (CAMEL_OBJECT (stream)); @@ -241,13 +362,22 @@ camel_imap_message_cache_get (CamelImapMessageCache *cache, const char *uid, } stream = camel_stream_fs_new_with_name (path, O_RDONLY, 0); - if (stream) + if (stream) { + printf ("got %s\n", path); cache_put (cache, uid, key, stream); + } g_free (path); return stream; } +/** + * camel_imap_message_cache_remove: + * @cache: the cache + * @uid: UID of the data to remove + * + * Removes all data associated with @uid from @cache. + **/ void camel_imap_message_cache_remove (CamelImapMessageCache *cache, const char *uid) { @@ -286,8 +416,50 @@ clear_part (gpointer key, gpointer value, gpointer data) return TRUE; } +/** + * camel_imap_message_cache_clear: + * @cache: the cache + * + * Removes all cached data from @cache. + **/ void camel_imap_message_cache_clear (CamelImapMessageCache *cache) { g_hash_table_foreach_remove (cache->parts, clear_part, cache); } + + +/** + * camel_imap_message_cache_copy: + * @source: the source message cache + * @source_uid: UID of a message in @source + * @dest: the destination message cache + * @dest_uid: UID of the message in @dest + * + * Copies all cached parts from @source_uid in @source to @dest_uid in + * @destination. + **/ +void +camel_imap_message_cache_copy (CamelImapMessageCache *source, + const char *source_uid, + CamelImapMessageCache *dest, + const char *dest_uid) +{ + GPtrArray *subparts; + CamelStream *stream; + char *part; + int i; + + subparts = g_hash_table_lookup (source->parts, source_uid); + if (!subparts || !subparts->len) + return; + + for (i = 0; i < subparts->len; i++) { + part = strchr (subparts->pdata[i], '.'); + if (!part++) + continue; + stream = camel_imap_message_cache_get (source, source_uid, part); + camel_imap_message_cache_insert_stream (dest, dest_uid, part, stream); + camel_object_unref (CAMEL_OBJECT (stream)); + } +} diff --git a/camel/providers/imap/camel-imap-message-cache.h b/camel/providers/imap/camel-imap-message-cache.h index 66e3adbaaa..c9af369a56 100644 --- a/camel/providers/imap/camel-imap-message-cache.h +++ b/camel/providers/imap/camel-imap-message-cache.h @@ -68,6 +68,15 @@ CamelStream *camel_imap_message_cache_insert (CamelImapMessageCache *cache, const char *part_spec, const char *data, int len); +void camel_imap_message_cache_insert_stream (CamelImapMessageCache *cache, + const char *uid, + const char *part_spec, + CamelStream *data_stream); +void camel_imap_message_cache_insert_wrapper (CamelImapMessageCache *cache, + const char *uid, + const char *part_spec, + CamelDataWrapper *wrapper); + CamelStream *camel_imap_message_cache_get (CamelImapMessageCache *cache, const char *uid, const char *part_spec); @@ -76,6 +85,11 @@ void camel_imap_message_cache_remove (CamelImapMessageCache *cache, void camel_imap_message_cache_clear (CamelImapMessageCache *cache); +void camel_imap_message_cache_copy (CamelImapMessageCache *source, + const char *source_uid, + CamelImapMessageCache *dest, + const char *dest_uid); + /* Standard Camel function */ CamelType camel_imap_message_cache_get_type (void); diff --git a/camel/providers/imap/camel-imap-utils.c b/camel/providers/imap/camel-imap-utils.c index 22f3c95b50..59d6ac5ea8 100644 --- a/camel/providers/imap/camel-imap-utils.c +++ b/camel/providers/imap/camel-imap-utils.c @@ -611,7 +611,7 @@ imap_uid_array_to_set (CamelFolderSummary *summary, GPtrArray *uids) if (++si < scount) next_summary_uid = get_summary_uid_numeric (summary, si); else - next_summary_uid = (guint32) -1; + next_summary_uid = (unsigned long) -1; /* Now get the next UID from @uids */ this_uid = strtoul (uids->pdata[ui], NULL, 10); @@ -620,7 +620,7 @@ imap_uid_array_to_set (CamelFolderSummary *summary, GPtrArray *uids) if (++si < scount) next_summary_uid = get_summary_uid_numeric (summary, si); else - next_summary_uid = (guint32) -1; + next_summary_uid = (unsigned long) -1; } else { if (range) { g_string_sprintfa (gset, ":%lu", last_uid); @@ -640,3 +640,92 @@ imap_uid_array_to_set (CamelFolderSummary *summary, GPtrArray *uids) return set; } + +/** + * imap_uid_set_to_array: + * @summary: summary for the folder the UIDs come from + * @uids: a pointer to the start of an IMAP "set" of UIDs + * + * Fills an array with the UIDs corresponding to @uids and @summary. + * There can be text after the uid set in @uids, which will be + * ignored. + * + * If @uids specifies a range of UIDs that extends outside the range + * of @summary, the function will assume that all of the "missing" UIDs + * do exist. + * + * Return value: the array of uids, which the caller must free with + * imap_uid_array_free(). (Or %NULL if the uid set can't be parsed.) + **/ +GPtrArray * +imap_uid_set_to_array (CamelFolderSummary *summary, const char *uids) +{ + GPtrArray *arr; + char *p, *q; + unsigned long uid, suid; + int si, scount; + + arr = g_ptr_array_new (); + scount = camel_folder_summary_count (summary); + + p = (char *)uids; + si = 0; + do { + uid = strtoul (p, &q, 10); + if (p == q) + goto lose; + g_ptr_array_add (arr, g_strndup (p, q - p)); + + if (*q == ':') { + /* Find the summary entry for the UID after the one + * we just saw. + */ + while (++si < scount) { + suid = get_summary_uid_numeric (summary, si); + if (suid > uid) + break; + } + if (si >= scount) + suid = uid + 1; + + uid = strtoul (q + 1, &p, 10); + if (p == q + 1) + goto lose; + + /* Add each summary UID until we find one + * larger than the end of the range + */ + while (suid <= uid) { + g_ptr_array_add (arr, g_strdup_printf ("%lu", suid)); + if (++si < scount) + suid = get_summary_uid_numeric (summary, si); + else + suid++; + } + } else + p = q; + } while (*p++ == ','); + + return arr; + + lose: + g_warning ("Invalid uid set %s", uids); + imap_uid_array_free (arr); + return NULL; +} + +/** + * imap_uid_array_free: + * @arr: an array returned from imap_uid_set_to_array() + * + * Frees @arr + **/ +void +imap_uid_array_free (GPtrArray *arr) +{ + int i; + + for (i = 0; i < arr->len; i++) + g_free (arr->pdata[i]); + g_ptr_array_free (arr, TRUE); +} diff --git a/camel/providers/imap/camel-imap-utils.h b/camel/providers/imap/camel-imap-utils.h index 937bec02d1..583505a95c 100644 --- a/camel/providers/imap/camel-imap-utils.h +++ b/camel/providers/imap/camel-imap-utils.h @@ -58,6 +58,8 @@ char *imap_quote_string (const char *str); void imap_skip_list (char **str_p); char * imap_uid_array_to_set (CamelFolderSummary *summary, GPtrArray *uids); +GPtrArray *imap_uid_set_to_array (CamelFolderSummary *summary, const char *uids); +void imap_uid_array_free (GPtrArray *arr); #ifdef __cplusplus } -- cgit v1.2.3