diff options
Diffstat (limited to 'camel')
-rw-r--r-- | camel/ChangeLog | 74 | ||||
-rw-r--r-- | camel/camel-folder-search.c | 1 | ||||
-rw-r--r-- | camel/camel-store-summary.c | 20 | ||||
-rw-r--r-- | camel/camel-store-summary.h | 8 | ||||
-rw-r--r-- | camel/camel-string-utils.c | 33 | ||||
-rw-r--r-- | camel/camel-string-utils.h | 1 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-folder.c | 262 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-folder.h | 2 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-grouplist.c | 3 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-store-summary.c | 18 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-store-summary.h | 2 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-store.c | 494 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-store.h | 44 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-summary.c | 386 | ||||
-rw-r--r-- | camel/providers/nntp/camel-nntp-summary.h | 23 |
15 files changed, 734 insertions, 637 deletions
diff --git a/camel/ChangeLog b/camel/ChangeLog index 465c17777f..a95bda954e 100644 --- a/camel/ChangeLog +++ b/camel/ChangeLog @@ -1,3 +1,77 @@ +2004-06-03 Not Zed <NotZed@Ximian.com> + + * providers/nntp/camel-nntp-folder.c (nntp_folder_sync_online): + only save the summary, don't update from server, thats what + refresh info does. + (nntp_folder_download_message): fix exception handling. + (nntp_folder_cache_message): same. + (nntp_folder_get_message): ditto, plus major cleanup. + (nntp_folder_download_message): take combined uid so it can cache + and lookup properly. duh. + + * providers/nntp/camel-nntp-store.c + (nntp_store_get_subscribed_folder_info): if not fast, then open + the folder, and update it. Yeah i've given up trying to worry + about performance vs usability. + + * providers/nntp/camel-nntp-summary.c (camel_nntp_summary_check): + update the storesummary if we update the folder summary. Hmm, + isn't duplicated data meant to be a bad thing? :P + + * providers/nntp/camel-nntp-store.c (camel_nntp_store_set_folder): + removed, now handled by nntp_command. + (nntp_connected): removed, now handled by nntp_command. + + * camel-string-utils.c (camel_tolower): added ascii to-lower + function. + (camel_toupper): and upper, for completeness. + + * camel-store-summary.c (CAMEL_STORE_SUMMARY_VERSION): bumped file + version by 1. This is a mess, version 1 files treated the + bitfield 'flags' with bit number values not bits. Messy. + + * providers/nntp/camel-nntp-store-summary.c (store_info_save): + write last/first count. + (CAMEL_NNTP_STORE_SUMMARY_VERSION): bump version to 1. + (store_info_load): if we're loading >= version 1, then load + last/first counts. + + * providers/nntp/camel-nntp-store.c + (nntp_store_get_folder_info_all): pass the whole line to + store_info_from_line, dont strip last/first info. + (nntp_store_info_update): renamed from info_new_from_line. only + add if not present. handle updates, try and handle unread counts + and readonly status. + +2004-06-02 Not Zed <NotZed@Ximian.com> + + * providers/nntp/camel-nntp-store.c: setup xover once we've + started. + + * providers/nntp/camel-nntp-summary.c: (xover_setup): moved to + nntp store. + + * providers/nntp/camel-nntp-folder.c (folder_check) + (folder_check_free, camel_nntp_folder_new): remove async summary + stuff. + + * providers/nntp/camel-nntp-store.c (camel_nntp_command): take + exception argument again, and folder argument. do retry logic and + auth logic differently. + (camel_nntp_raw_command): raw command interface, dont try + reconnect or anything fancy. pass i/o errors straight out, etc. + (camel_nntp_try_authenticate): change to return return codes & + take exception. + + * providers/nntp/camel-nntp-summary.c (camel_nntp_summary_new): + just take path argument. + (camel_nntp_summary_check): take a store, and a folder name. + (add_range_head, add_range_xover): remove the time based update + events, they never had any effect anyway. Take store argument. + (xover_setup): take store argument. + + * camel-folder-search.c (search_match_threads): remove debug. + 2004-06-01 Not Zed <NotZed@Ximian.com> ** A few fixes for better rfc compliance, and cleaner code. diff --git a/camel/camel-folder-search.c b/camel/camel-folder-search.c index 26e789916a..ab901f3cc8 100644 --- a/camel/camel-folder-search.c +++ b/camel/camel-folder-search.c @@ -780,7 +780,6 @@ search_match_threads(struct _ESExp *f, int argc, struct _ESExpTerm **argv, Camel r = e_sexp_term_eval(f, argv[i]); } - printf("match-threads, result %d\n", r==NULL?-1:r->type); if (r == NULL || r->type != ESEXP_RES_ARRAY_PTR) e_sexp_fatal_error(f, _("(match-threads) expects an array result")); diff --git a/camel/camel-store-summary.c b/camel/camel-store-summary.c index 86e42fc98c..982d0ed2b1 100644 --- a/camel/camel-store-summary.c +++ b/camel/camel-store-summary.c @@ -47,9 +47,10 @@ /* possible versions, for versioning changes */ #define CAMEL_STORE_SUMMARY_VERSION_0 (1) +#define CAMEL_STORE_SUMMARY_VERSION_2 (2) /* current version */ -#define CAMEL_STORE_SUMMARY_VERSION (1) +#define CAMEL_STORE_SUMMARY_VERSION (2) #define _PRIVATE(o) (((CamelStoreSummary *)(o))->priv) @@ -822,6 +823,23 @@ store_info_load(CamelStoreSummary *s, FILE *in) camel_file_util_decode_uint32(in, &mi->unread); camel_file_util_decode_uint32(in, &mi->total); + /* Ok, brown paper bag bug - prior to version 2 of the file, flags are + stored using the bit number, not the bit. Try to recover as best we can */ + if (s->version < CAMEL_STORE_SUMMARY_VERSION_2) { + guint32 flags = 0; + + if (mi->flags & 1) + flags |= CAMEL_STORE_INFO_FOLDER_NOSELECT; + if (mi->flags & 2) + flags |= CAMEL_STORE_INFO_FOLDER_READONLY; + if (mi->flags & 3) + flags |= CAMEL_STORE_INFO_FOLDER_SUBSCRIBED; + if (mi->flags & 4) + flags |= CAMEL_STORE_INFO_FOLDER_FLAGGED; + + mi->flags = flags; + } + if (!ferror(in)) return mi; diff --git a/camel/camel-store-summary.h b/camel/camel-store-summary.h index dbcd6119b7..e512cc32ba 100644 --- a/camel/camel-store-summary.h +++ b/camel/camel-store-summary.h @@ -46,10 +46,10 @@ typedef struct _CamelStoreSummaryClass CamelStoreSummaryClass; typedef struct _CamelStoreInfo CamelStoreInfo; enum _CamelStoreInfoFlags { - CAMEL_STORE_INFO_FOLDER_NOSELECT, - CAMEL_STORE_INFO_FOLDER_READONLY, - CAMEL_STORE_INFO_FOLDER_SUBSCRIBED, - CAMEL_STORE_INFO_FOLDER_FLAGGED, + CAMEL_STORE_INFO_FOLDER_NOSELECT = 1<<0, + CAMEL_STORE_INFO_FOLDER_READONLY = 1<<1, + CAMEL_STORE_INFO_FOLDER_SUBSCRIBED = 1<<2, + CAMEL_STORE_INFO_FOLDER_FLAGGED = 1<<3, }; #define CAMEL_STORE_INFO_FOLDER_UNKNOWN (~0) diff --git a/camel/camel-string-utils.c b/camel/camel-string-utils.c index d9927d629a..6949a9aa53 100644 --- a/camel/camel-string-utils.c +++ b/camel/camel-string-utils.c @@ -108,3 +108,36 @@ camel_strdown (char *str) return str; } + +/** + * camel_tolower: + * @c: + * + * ASCII to-lower function. + * + * Return value: + **/ +char camel_tolower(char c) +{ + if (c >= 'A' && c <= 'Z') + c |= 0x20; + + return c; +} + +/** + * camel_toupper: + * @c: + * + * ASCII to-upper function. + * + * Return value: + **/ +char camel_toupper(char c) +{ + if (c >= 'a' && c <= 'z') + c &= ~0x20; + + return c; +} + diff --git a/camel/camel-string-utils.h b/camel/camel-string-utils.h index 54cbad3fad..e8a5bf3ffb 100644 --- a/camel/camel-string-utils.h +++ b/camel/camel-string-utils.h @@ -39,6 +39,7 @@ void camel_string_list_free (GList *string_list); char *camel_strstrcase (const char *haystack, const char *needle); const char *camel_strdown (char *str); +char camel_tolower(char c); #ifdef __cplusplus } diff --git a/camel/providers/nntp/camel-nntp-folder.c b/camel/providers/nntp/camel-nntp-folder.c index 411cc346b5..04855a2a78 100644 --- a/camel/providers/nntp/camel-nntp-folder.c +++ b/camel/providers/nntp/camel-nntp-folder.c @@ -65,21 +65,29 @@ static CamelDiscoFolderClass *parent_class = NULL; #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so)) #define CNNTPS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so)) +void +camel_nntp_folder_selected(CamelNNTPFolder *folder, char *line, CamelException *ex) +{ + camel_nntp_summary_check((CamelNNTPSummary *)((CamelFolder *)folder)->summary, + (CamelNNTPStore *)((CamelFolder *)folder)->parent_store, + line, folder->changes, ex); +} + static void nntp_folder_refresh_info_online (CamelFolder *folder, CamelException *ex) { CamelNNTPStore *nntp_store; CamelFolderChangeInfo *changes = NULL; CamelNNTPFolder *nntp_folder; - + char *line; + nntp_store = (CamelNNTPStore *) folder->parent_store; nntp_folder = (CamelNNTPFolder *) folder; CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock); - - if (camel_nntp_summary_check ((CamelNNTPSummary *) folder->summary, nntp_folder->changes, ex) != -1) - camel_folder_summary_save (folder->summary); - + + camel_nntp_command(nntp_store, ex, nntp_folder, &line, NULL); + if (camel_folder_change_info_changed(nntp_folder->changes)) { changes = nntp_folder->changes; nntp_folder->changes = camel_folder_change_info_new(); @@ -96,35 +104,17 @@ nntp_folder_refresh_info_online (CamelFolder *folder, CamelException *ex) static void nntp_folder_sync_online (CamelFolder *folder, CamelException *ex) { - CamelNNTPStore *nntp_store; - CamelFolderChangeInfo *changes = NULL; - CamelNNTPFolder *nntp_folder; - - nntp_store = (CamelNNTPStore *) folder->parent_store; - nntp_folder = (CamelNNTPFolder *) folder; - - CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock); - - if (camel_nntp_summary_check ((CamelNNTPSummary *) folder->summary, nntp_folder->changes, ex) != -1) - camel_folder_summary_save (folder->summary); - - if (camel_folder_change_info_changed(nntp_folder->changes)) { - changes = nntp_folder->changes; - nntp_folder->changes = camel_folder_change_info_new(); - } - - CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); - - if (changes) { - camel_object_trigger_event ((CamelObject *) folder, "folder_changed", changes); - camel_folder_change_info_free (changes); - } + CAMEL_NNTP_STORE_LOCK(folder->parent_store, command_lock); + camel_folder_summary_save (folder->summary); + CAMEL_NNTP_STORE_UNLOCK(folder->parent_store, command_lock); } static void nntp_folder_sync_offline (CamelFolder *folder, CamelException *ex) { + CAMEL_NNTP_STORE_LOCK(folder->parent_store, command_lock); camel_folder_summary_save (folder->summary); + CAMEL_NNTP_STORE_UNLOCK(folder->parent_store, command_lock); } static gboolean @@ -134,20 +124,14 @@ nntp_folder_set_message_flags (CamelFolder *folder, const char *uid, guint32 fla } static CamelStream * -nntp_folder_download_message (CamelNNTPFolder *nntp_folder, const char *msgid, CamelException *ex) +nntp_folder_download_message (CamelNNTPFolder *nntp_folder, const char *id, const char *msgid, CamelException *ex) { CamelNNTPStore *nntp_store = (CamelNNTPStore *) ((CamelFolder *) nntp_folder)->parent_store; CamelStream *stream = NULL; int ret; char *line; - - if (camel_nntp_store_set_folder (nntp_store, (CamelFolder *) nntp_folder, nntp_folder->changes, ex) == -1) - return NULL; - - ret = camel_nntp_command (nntp_store, &line, "article %s", msgid); - if (ret == -1) - goto fail; - + + ret = camel_nntp_command (nntp_store, ex, nntp_folder, &line, "article %s", id); if (ret == 220) { stream = camel_data_cache_add (nntp_store->cache, "cache", msgid, NULL); if (stream) { @@ -159,6 +143,10 @@ nntp_folder_download_message (CamelNNTPFolder *nntp_folder, const char *msgid, C stream = (CamelStream *) nntp_store->stream; camel_object_ref (stream); } + } else if (ret == 423 || ret == 430) { + camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID, _("Cannot get message %s: %s"), msgid, line); + } else if (ret != -1) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), msgid, line); } return stream; @@ -192,15 +180,10 @@ nntp_folder_cache_message (CamelDiscoFolder *disco_folder, const char *uid, Came CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock); - stream = nntp_folder_download_message ((CamelNNTPFolder *) disco_folder, article, ex); - if (stream) { + stream = nntp_folder_download_message ((CamelNNTPFolder *) disco_folder, article, msgid, ex); + if (stream) camel_object_unref (stream); - } else { - /* failed to download message! */ - camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not get article %s from NNTP server"), uid); - } - + CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); } @@ -212,23 +195,22 @@ nntp_folder_get_message (CamelFolder *folder, const char *uid, CamelException *e CamelFolderChangeInfo *changes; CamelNNTPFolder *nntp_folder; CamelStream *stream = NULL; - char *line = NULL; char *article, *msgid; nntp_store = (CamelNNTPStore *) folder->parent_store; nntp_folder = (CamelNNTPFolder *) folder; - CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock); - article = alloca(strlen(uid)+1); strcpy(article, uid); msgid = strchr (article, ','); if (msgid == NULL) { camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Internal error: uid in invalid format: %s"), uid); - goto fail; + return NULL; } *msgid++ = 0; + + CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock); /* Lookup in cache, NEWS is global messageid's so use a global cache path */ stream = camel_data_cache_get (nntp_store->cache, "cache", msgid, NULL); @@ -239,38 +221,23 @@ nntp_folder_get_message (CamelFolder *folder, const char *uid, CamelException *e goto fail; } - stream = nntp_folder_download_message (nntp_folder, article, ex); + stream = nntp_folder_download_message (nntp_folder, article, msgid, ex); if (stream == NULL) goto fail; } - if (stream) { - message = camel_mime_message_new (); - if (camel_data_wrapper_construct_from_stream ((CamelDataWrapper *) message, stream) == -1) - goto error; - - CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); - - camel_object_unref (stream); - - return message; + 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 cancelled")); + else + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, g_strerror (errno)); + camel_object_unref(message); + message = NULL; } - - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, line); - - error: - if (errno == EINTR) - camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled")); - else - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot get message %s: %s"), uid, g_strerror (errno)); - - fail: - if (message) - camel_object_unref (message); - - if (stream) - camel_object_unref (stream); - + + camel_object_unref (stream); +fail: if (camel_folder_change_info_changed (nntp_folder->changes)) { changes = nntp_folder->changes; nntp_folder->changes = camel_folder_change_info_new (); @@ -284,8 +251,8 @@ nntp_folder_get_message (CamelFolder *folder, const char *uid, CamelException *e camel_object_trigger_event ((CamelObject *) folder, "folder_changed", changes); camel_folder_change_info_free (changes); } - - return NULL; + + return message; } static GPtrArray* @@ -349,33 +316,25 @@ nntp_folder_append_message_online (CamelFolder *folder, CamelMimeMessage *mime_m int ret; unsigned int u; struct _camel_header_raw *header, *savedhdrs, *n, *tail; - unsigned char *line; - char *cmdbuf = NULL, *respbuf = NULL; + char *group, *line; CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock); /* send 'POST' command */ - ret = camel_nntp_command (nntp_store, (char **) &line, "post"); - + ret = camel_nntp_command (nntp_store, ex, NULL, &line, "post"); if (ret != 340) { - camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, - _("Posting not allowed by news server")); + if (ret == 440) + camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INSUFFICIENT_PERMISSION, + _("Posting failed: %s"), line); + else if (ret != -1) + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, + _("Posting failed: %s"), line); CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); return; } - /* send the 'Newsgroups: ' header */ - cmdbuf = g_strdup_printf ("Newsgroups: %s\r\n", folder->full_name); - - if (camel_stream_write (stream, cmdbuf, strlen (cmdbuf)) == -1) { - g_free (cmdbuf); - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Failed to send newsgroups header: %s: message not posted"), - g_strerror (errno)); - CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); - return; - } - g_free (cmdbuf); + /* the 'Newsgroups: ' header */ + group = g_strdup_printf ("Newsgroups: %s\r\n", folder->full_name); /* setup stream filtering */ crlffilter = camel_mime_filter_crlf_new (CAMEL_MIME_FILTER_CRLF_ENCODE, CAMEL_MIME_FILTER_CRLF_MODE_CRLF_DOTS); @@ -403,46 +362,23 @@ nntp_folder_append_message_online (CamelFolder *folder, CamelMimeMessage *mime_m } /* write the message */ - ret = camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (mime_message), CAMEL_STREAM (filtered_stream)); - - /* restore the mail headers */ - header->next = savedhdrs; - - if (ret == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Error posting to newsgroup: %s: message not posted"), - g_strerror (errno)); - camel_object_unref (filtered_stream); - CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); - return; - } - - camel_stream_flush (CAMEL_STREAM (filtered_stream)); - camel_object_unref (filtered_stream); - - /* terminate the message body */ - if (camel_stream_write (stream, "\r\n.\r\n", 5) == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Error posting to newsgroup: %s: message not posted"), - g_strerror (errno)); - CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); - return; - } - - if (camel_nntp_stream_line (nntp_store->stream, (unsigned char **) &respbuf, &u) == -1) - respbuf = NULL; - - if (!respbuf || strncmp (respbuf, "240", 3)) { - if (!respbuf) - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Error reading response to posted message: message not posted")); + if (camel_stream_write(stream, group, strlen(group)) == -1 + || camel_data_wrapper_write_to_stream (CAMEL_DATA_WRAPPER (mime_message), CAMEL_STREAM (filtered_stream)) == -1 + || camel_stream_flush (CAMEL_STREAM (filtered_stream)) == -1 + || camel_stream_write (stream, "\r\n.\r\n", 5) == -1 + || (ret = camel_nntp_stream_line (nntp_store->stream, (unsigned char **)&line, &u)) == -1) { + if (errno == EINTR) + camel_exception_setv (ex, CAMEL_EXCEPTION_USER_CANCEL, _("User cancelled")); else - camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, - _("Error posting message: %s: message not posted"), respbuf); - CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); - return; + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Posting failed: %s"), g_strerror (errno)); + } else if (atoi(line) != 240) { + camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, _("Posting failed: %s"), line); } - + + camel_object_unref (filtered_stream); + g_free(group); + header->next = savedhdrs; + CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); return; @@ -541,47 +477,6 @@ camel_nntp_folder_get_type (void) return camel_nntp_folder_type; } - -/* not yet */ -/* Idea is we update in stages, but this requires a different xover command, etc */ -#ifdef ASYNC_SUMMARY -struct _folder_check_msg { - CamelSessionThreadMsg msg; - CamelNNTPFolder *folder; -}; - -static void -folder_check(CamelSession *session, CamelSessionThreadMsg *msg) -{ - struct _folder_check_msg *m = (struct _folder_check_msg *)msg; - CamelException *ex; - CamelNNTPStore *nntp_store; - - nntp_store = (CamelNNTPStore *) m->folder->parent.parent_store; - - CAMEL_NNTP_STORE_LOCK(nntp_store, command_lock); - - ex = camel_exception_new (); - camel_nntp_summary_check ((CamelNNTPSummary *) m->folder->parent.summary, m->folder->changes, ex); - camel_exception_free (ex); - - CAMEL_NNTP_STORE_UNLOCK(nntp_store, command_lock); -} - -static void -folder_check_free(CamelSession *session, CamelSessionThreadMsg *msg) -{ - struct _folder_check_msg *m = (struct _folder_check_msg *)msg; - - camel_object_unref (m->folder); -} - -static CamelSessionThreadOps folder_check_ops = { - folder_check, - folder_check_free, -}; -#endif - CamelFolder * camel_nntp_folder_new (CamelStore *parent, const char *folder_name, CamelException *ex) { @@ -589,9 +484,6 @@ camel_nntp_folder_new (CamelStore *parent, const char *folder_name, CamelExcepti CamelNNTPFolder *nntp_folder; char *root; CamelService *service; -#ifdef ASYNC_SUMMARY - struct _folder_check_msg *m; -#endif CamelStoreInfo *si; gboolean subscribed = TRUE; @@ -617,7 +509,9 @@ camel_nntp_folder_new (CamelStore *parent, const char *folder_name, CamelExcepti camel_object_state_read(nntp_folder); g_free(root); - folder->summary = (CamelFolderSummary *) camel_nntp_summary_new (nntp_folder); + root = g_strdup_printf("%s.ev-summary", nntp_folder->storage_path); + folder->summary = (CamelFolderSummary *) camel_nntp_summary_new (root); + g_free(root); camel_folder_summary_load (folder->summary); si = camel_store_summary_path ((CamelStoreSummary *) ((CamelNNTPStore*) parent)->summary, folder_name); @@ -627,17 +521,11 @@ camel_nntp_folder_new (CamelStore *parent, const char *folder_name, CamelExcepti } if (subscribed) { -#ifdef ASYNC_SUMMARY - m = camel_session_thread_msg_new (service->session, &folder_check_ops, sizeof(*m)); - m->folder = nntp_folder; - camel_object_ref (folder); - camel_session_thread_queue (service->session, &m->msg, 0); -#else - if (camel_nntp_summary_check ((CamelNNTPSummary *) folder->summary, nntp_folder->changes, ex) == -1) { + camel_folder_refresh_info(folder, ex); + if (camel_exception_is_set(ex)) { camel_object_unref (folder); folder = NULL; } -#endif } return folder; diff --git a/camel/providers/nntp/camel-nntp-folder.h b/camel/providers/nntp/camel-nntp-folder.h index 378fee69cc..0914ee4cad 100644 --- a/camel/providers/nntp/camel-nntp-folder.h +++ b/camel/providers/nntp/camel-nntp-folder.h @@ -66,6 +66,8 @@ CamelType camel_nntp_folder_get_type (void); CamelFolder *camel_nntp_folder_new (CamelStore *parent, const char *folder_name, CamelException *ex); +void camel_nntp_folder_selected(CamelNNTPFolder *folder, char *line, CamelException *ex); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/camel/providers/nntp/camel-nntp-grouplist.c b/camel/providers/nntp/camel-nntp-grouplist.c index 7f3850f9c1..cbcf2b30b2 100644 --- a/camel/providers/nntp/camel-nntp-grouplist.c +++ b/camel/providers/nntp/camel-nntp-grouplist.c @@ -37,8 +37,7 @@ camel_nntp_get_grouplist_from_server (CamelNNTPStore *store, CamelException *ex) CamelNNTPGroupList *list; CAMEL_NNTP_STORE_LOCK(store); - status = camel_nntp_command (store, ex, NULL, - "LIST"); + status = camel_nntp_command (store, ex, NULL, &line, "LIST"); if (status != NNTP_LIST_FOLLOWS) { camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM, diff --git a/camel/providers/nntp/camel-nntp-store-summary.c b/camel/providers/nntp/camel-nntp-store-summary.c index 8cade2a5eb..7dbcd89118 100644 --- a/camel/providers/nntp/camel-nntp-store-summary.c +++ b/camel/providers/nntp/camel-nntp-store-summary.c @@ -19,8 +19,6 @@ * Boston, MA 02111-1307, USA. */ -/* currently, this is just a straigt s/imap/nntp from the IMAP file*/ - #ifdef HAVE_CONFIG_H #include <config.h> #endif @@ -45,8 +43,9 @@ #define io(x) /* io debug */ #define CAMEL_NNTP_STORE_SUMMARY_VERSION_0 (0) +#define CAMEL_NNTP_STORE_SUMMARY_VERSION_1 (1) -#define CAMEL_NNTP_STORE_SUMMARY_VERSION (0) +#define CAMEL_NNTP_STORE_SUMMARY_VERSION (1) #define _PRIVATE(o) (((CamelNNTPStoreSummary *)(o))->priv) @@ -362,7 +361,14 @@ store_info_load (CamelStoreSummary *s, FILE *in) if (ni) { if (camel_file_util_decode_string (in, &ni->full_name) == -1) { camel_store_summary_info_free (s, (CamelStoreInfo *) ni); - ni = NULL; + return NULL; + } + if (((CamelNNTPStoreSummary *)s)->version >= CAMEL_NNTP_STORE_SUMMARY_VERSION_1) { + if (camel_file_util_decode_uint32(in, &ni->first) == -1 + || camel_file_util_decode_uint32(in, &ni->last) == -1) { + camel_store_summary_info_free (s, (CamelStoreInfo *) ni); + return NULL; + } } /* set the URL */ } @@ -376,7 +382,9 @@ store_info_save (CamelStoreSummary *s, FILE *out, CamelStoreInfo *mi) CamelNNTPStoreInfo *isi = (CamelNNTPStoreInfo *)mi; if (camel_nntp_store_summary_parent->store_info_save (s, out, mi) == -1 - || camel_file_util_encode_string (out, isi->full_name) == -1) + || camel_file_util_encode_string (out, isi->full_name) == -1 + || camel_file_util_encode_uint32(out, isi->first) == -1 + || camel_file_util_encode_uint32(out, isi->last) == -1) return -1; return 0; diff --git a/camel/providers/nntp/camel-nntp-store-summary.h b/camel/providers/nntp/camel-nntp-store-summary.h index cf0401f809..2e442a8166 100644 --- a/camel/providers/nntp/camel-nntp-store-summary.h +++ b/camel/providers/nntp/camel-nntp-store-summary.h @@ -50,6 +50,8 @@ enum { struct _CamelNNTPStoreInfo { CamelStoreInfo info; char *full_name; + guint32 first; /* from LIST or NEWGROUPS return */ + guint32 last; }; #define NNTP_DATE_SIZE 14 diff --git a/camel/providers/nntp/camel-nntp-store.c b/camel/providers/nntp/camel-nntp-store.c index 26e825436b..a36a69634f 100644 --- a/camel/providers/nntp/camel-nntp-store.c +++ b/camel/providers/nntp/camel-nntp-store.c @@ -40,6 +40,9 @@ #include <camel/camel-tcp-stream-raw.h> #include <camel/camel-tcp-stream-ssl.h> +#include <camel/camel-stream-mem.h> +#include <camel/camel-data-cache.h> + #include <camel/camel-disco-store.h> #include <camel/camel-disco-diary.h> @@ -84,6 +87,72 @@ enum { USE_SSL_WHEN_POSSIBLE }; +static struct { + const char *name; + int type; +} headers[] = { + { "subject", 0 }, + { "from", 0 }, + { "date", 0 }, + { "message-id", 1 }, + { "references", 0 }, + { "bytes", 2 }, +}; + +static int +xover_setup(CamelNNTPStore *store, CamelException *ex) +{ + int ret, i; + char *line; + unsigned int len; + unsigned char c, *p; + struct _xover_header *xover, *last; + + /* manual override */ + if (store->xover || getenv("CAMEL_NNTP_DISABLE_XOVER") != NULL) + return 0; + + ret = camel_nntp_raw_command(store, ex, &line, "list overview.fmt"); + if (ret == -1) { + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, + _("NNTP Command failed: %s"), g_strerror(errno)); + return -1; + } else if (ret != 215) + /* unsupported command? ignore */ + return 0; + + last = (struct _xover_header *)&store->xover; + + /* supported command */ + while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) { + p = line; + xover = g_malloc0(sizeof(*xover)); + last->next = xover; + last = xover; + while ((c = *p++)) { + if (c == ':') { + p[-1] = 0; + for (i=0;i<sizeof(headers)/sizeof(headers[0]);i++) { + if (strcmp(line, headers[i].name) == 0) { + xover->name = headers[i].name; + if (strncmp(p, "full", 4) == 0) + xover->skip = strlen(xover->name)+1; + else + xover->skip = 0; + xover->type = headers[i].type; + break; + } + } + break; + } else { + p[-1] = camel_tolower(c); + } + } + } + + return ret; +} + static gboolean connect_to_server (CamelService *service, int ssl_mode, CamelException *ex) { @@ -176,12 +245,13 @@ connect_to_server (CamelService *service, int ssl_mode, CamelException *ex) goto fail; } - /* set 'reader' mode & ignore return code */ - if (camel_nntp_command (store, (char **) &buf, "mode reader") < 0 || - /* hack: inn seems to close connections if nothing is done within - the first ten seconds. a non-existent command is enough though */ - camel_nntp_command (store, (char **) &buf, "date") < 0) - goto fail; + /* set 'reader' mode & ignore return code, also ping the server, inn goes offline very quickly otherwise */ + if (camel_nntp_raw_command (store, ex, (char **) &buf, "mode reader") == -1 + || camel_nntp_raw_command (store, ex, (char **) &buf, "date") == -1) + goto fail; + + if (xover_setup(store, ex) == -1) + goto fail; path = g_build_filename (store->storage_path, ".ev-journal", NULL); disco_store->diary = camel_disco_diary_new (disco_store, path, ex); @@ -285,8 +355,10 @@ nntp_disconnect_online (CamelService *service, gboolean clean, CamelException *e CAMEL_NNTP_STORE_LOCK(store, command_lock); - if (clean) - camel_nntp_command (store, &line, "quit"); + if (clean) { + camel_nntp_raw_command (store, ex, &line, "quit"); + camel_exception_clear(ex); + } if (!service_class->disconnect (service, clean, ex)) { CAMEL_NNTP_STORE_UNLOCK(store, command_lock); @@ -399,7 +471,8 @@ nntp_folder_info_from_store_info (CamelNNTPStore *store, gboolean short_notation else fi->name = g_strdup (si->path); - fi->unread = -1; + fi->unread = si->unread; + fi->total = si->total; path = alloca(strlen(fi->full_name)+2); sprintf(path, "/%s", fi->full_name); url = camel_url_new_with_base (base_url, path); @@ -435,25 +508,69 @@ nntp_folder_info_from_name (CamelNNTPStore *store, gboolean short_notation, cons return fi; } -static CamelStoreInfo * -nntp_store_info_from_line (CamelNNTPStore *store, char *line) +/* handle list/newgroups response */ +static CamelNNTPStoreInfo * +nntp_store_info_update(CamelNNTPStore *store, char *line) { CamelStoreSummary *summ = (CamelStoreSummary *)store->summary; CamelURL *base_url = ((CamelService *)store)->url; - CamelNNTPStoreInfo *nsi = (CamelNNTPStoreInfo*)camel_store_summary_info_new(summ); - CamelStoreInfo *si = (CamelStoreInfo*)nsi; + CamelNNTPStoreInfo *si, *fsi; CamelURL *url; - char *relpath; - - relpath = g_strdup_printf ("/%s", line); - url = camel_url_new_with_base (base_url, relpath); - g_free (relpath); - si->uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL); - camel_url_free (url); - - si->path = g_strdup (line); - nsi->full_name = g_strdup (line); - return (CamelStoreInfo*) si; + char *relpath, *tmp; + guint32 last = 0, first = 0, new = 0; + + tmp = strchr(line, ' '); + if (tmp) + *tmp++ = 0; + + fsi = si = (CamelNNTPStoreInfo *)camel_store_summary_path((CamelStoreSummary *)store->summary, line); + if (si == NULL) { + si = (CamelNNTPStoreInfo*)camel_store_summary_info_new(summ); + + relpath = g_alloca(strlen(line)+2); + sprintf(relpath, "/%s", line); + url = camel_url_new_with_base (base_url, relpath); + si->info.uri = camel_url_to_string (url, CAMEL_URL_HIDE_ALL); + camel_url_free (url); + + si->info.path = g_strdup (line); + si->full_name = g_strdup (line); /* why do we keep this? */ + camel_store_summary_add((CamelStoreSummary *)store->summary, &si->info); + } else { + first = si->first; + last = si->last; + } + + if (tmp && *tmp >= '0' && *tmp <= '9') { + last = strtoul(tmp, &tmp, 10); + if (*tmp == ' ' && tmp[1] >= '0' && tmp[1] <= '9') { + first = strtoul(tmp+1, &tmp, 10); + if (*tmp == ' ' && tmp[1] != 'y') + si->info.flags |= CAMEL_STORE_INFO_FOLDER_READONLY; + } + } + + printf("store info update '%s' first '%d' last '%d'\n", line, first, last); + + if (si->last) { + if (last > si->last) + new = last-si->last; + } else { + if (last > first) + new = last - first; + } + + si->info.total = last > first?last-first:0; + si->info.unread += new; /* this is a _guess_ */ + si->last = last; + si->first = first; + + if (fsi) + camel_store_summary_info_free((CamelStoreSummary *)store->summary, &fsi->info); + else /* TODO see if we really did touch it */ + camel_store_summary_touch ((CamelStoreSummary *)store->summary); + + return si; } static CamelFolderInfo * @@ -468,10 +585,24 @@ nntp_store_get_subscribed_folder_info (CamelNNTPStore *store, const char *top, g return NULL; for (i=0;(si = camel_store_summary_index ((CamelStoreSummary *) store->summary, i));i++) { + if (si == NULL) + continue; + if (si->flags & CAMEL_STORE_INFO_FOLDER_SUBSCRIBED) { + /* slow mode? open and update the folder, always! this will implictly update + our storeinfo too; in a very round-about way */ + if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0) { + CamelNNTPFolder *folder; + char *line; + + folder = (CamelNNTPFolder *)camel_store_get_folder((CamelStore *)store, si->path, 0, ex); + if (folder) { + camel_nntp_command(store, ex, folder, &line, NULL); + camel_object_unref(folder); + } + camel_exception_clear(ex); + } fi = nntp_folder_info_from_store_info (store, store->do_short_folder_notation, si); - if (!fi) - continue; fi->flags |= CAMEL_FOLDER_NOINFERIORS | CAMEL_FOLDER_NOCHILDREN | CAMEL_FOLDER_SYSTEM; if (last) last->next = fi; @@ -552,12 +683,12 @@ nntp_store_get_cached_folder_info (CamelNNTPStore *store, const char *orig_top, /* retrieves the date from the NNTP server */ static gboolean -nntp_get_date(CamelNNTPStore *nntp_store) +nntp_get_date(CamelNNTPStore *nntp_store, CamelException *ex) { unsigned char *line; - int ret = camel_nntp_command(nntp_store, (char **)&line, "date"); + int ret = camel_nntp_command(nntp_store, ex, NULL, (char **)&line, "date"); char *ptr; - + nntp_store->summary->last_newslist[0] = 0; if (ret == 111) { @@ -573,6 +704,15 @@ nntp_get_date(CamelNNTPStore *nntp_store) return FALSE; } +static void +store_info_remove(void *key, void *value, void *data) +{ + CamelStoreSummary *summary = data; + CamelStoreInfo *si = value; + + camel_store_summary_remove(summary, si); +} + static gint store_info_sort (gconstpointer a, gconstpointer b) { @@ -583,9 +723,9 @@ static CamelFolderInfo * nntp_store_get_folder_info_all(CamelNNTPStore *nntp_store, const char *top, guint32 flags, gboolean online, CamelException *ex) { CamelNNTPStoreSummary *summary = nntp_store->summary; - CamelStoreInfo *si; + CamelNNTPStoreInfo *si; unsigned int len; - unsigned char *line, *space; + unsigned char *line; int ret = -1; if (top == NULL) @@ -600,53 +740,50 @@ nntp_store_get_folder_info_all(CamelNNTPStore *nntp_store, const char *top, guin memcpy(date + 7, summary->last_newslist + 8, 6); /* HHMMSS */ date[13] = '\0'; - nntp_get_date (nntp_store); + if (!nntp_get_date (nntp_store, ex)) + return NULL; - ret = camel_nntp_command (nntp_store, (char **) &line, "newgroups %s", date); - if (ret != 231) { + ret = camel_nntp_command (nntp_store, ex, NULL, (char **) &line, "newgroups %s", date); + if (ret == -1) + return NULL; + else if (ret != 231) { /* newgroups not supported :S so reload the complete list */ - ret = -1; - camel_store_summary_clear ((CamelStoreSummary*) summary); summary->last_newslist[0] = 0; goto do_complete_list; } - - while ((ret = camel_nntp_stream_line (nntp_store->stream, &line, &len)) > 0) { - if ((space = strchr(line, ' '))) - *space = '\0'; - - si = camel_store_summary_path ((CamelStoreSummary *) nntp_store->summary, line); - if (si) { - camel_store_summary_info_free ((CamelStoreSummary *) nntp_store->summary, si); - } else { - si = nntp_store_info_from_line (nntp_store, line); - camel_store_summary_add ((CamelStoreSummary*) nntp_store->summary, si); - } - } + + while ((ret = camel_nntp_stream_line (nntp_store->stream, &line, &len)) > 0) + nntp_store_info_update(nntp_store, line); } else { + GHashTable *all; + int i; + do_complete_list: /* seems we do need a complete list */ /* at first, we do a DATE to find out the last load occasion */ - nntp_get_date (nntp_store); + if (!nntp_get_date (nntp_store, ex)) + goto error; - ret = camel_nntp_command (nntp_store, (char **)&line, "list"); - if (ret != 215) { - if (ret < 0) - line = _("Stream error"); - ret = -1; + ret = camel_nntp_command (nntp_store, ex, NULL, (char **)&line, "list"); + if (ret == -1) + return NULL; + else if (ret != 215) { camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_INVALID, _("Error retrieving newsgroups:\n\n%s"), line); goto error; } - + + all = g_hash_table_new(g_str_hash, g_str_equal); + for (i = 0; (si = (CamelNNTPStoreInfo *)camel_store_summary_index ((CamelStoreSummary *)nntp_store->summary, i)); i++) + g_hash_table_insert(all, si->info.path, si); + while ((ret = camel_nntp_stream_line(nntp_store->stream, &line, &len)) > 0) { - if ((space = strchr(line, ' '))) - *space = '\0'; - - si = nntp_store_info_from_line (nntp_store, line); - camel_store_summary_add ((CamelStoreSummary*) nntp_store->summary, si); - /* check to see if it answers our current query */ + si = nntp_store_info_update(nntp_store, line); + g_hash_table_remove(all, si->info.path); } + + g_hash_table_foreach(all, store_info_remove, nntp_store->summary); + g_hash_table_destroy(all); } /* sort the list */ @@ -810,6 +947,7 @@ nntp_store_finalize (CamelObject *object) /* call base finalize */ CamelNNTPStore *nntp_store = CAMEL_NNTP_STORE (object); struct _CamelNNTPStorePrivate *p = nntp_store->priv; + struct _xover_header *xover, *xn; camel_service_disconnect ((CamelService *)object, TRUE, NULL); @@ -827,6 +965,13 @@ nntp_store_finalize (CamelObject *object) g_free (nntp_store->base_url); if (nntp_store->storage_path) g_free (nntp_store->storage_path); + + xover = nntp_store->xover; + while (xover) { + xn = xover->next; + g_free(xover); + xover = xn; + } e_mutex_destroy(p->command_lock); @@ -955,110 +1100,66 @@ camel_nntp_store_get_type (void) return camel_nntp_store_type; } -/* enter owning lock */ -int -camel_nntp_store_set_folder (CamelNNTPStore *store, CamelFolder *folder, CamelFolderChangeInfo *changes, CamelException *ex) -{ - int ret; - - if (store->current_folder && strcmp (folder->full_name, store->current_folder) == 0) - return 0; - - /* FIXME: Do something with changeinfo */ - ret = camel_nntp_summary_check ((CamelNNTPSummary *) folder->summary, changes, ex); - - g_free (store->current_folder); - store->current_folder = g_strdup (folder->full_name); - - return ret; -} - -static gboolean -camel_nntp_try_authenticate (CamelNNTPStore *store) +static int +camel_nntp_try_authenticate (CamelNNTPStore *store, CamelException *ex) { CamelService *service = (CamelService *) store; CamelSession *session = camel_service_get_session (service); int ret; char *line; - if (!service->url->user) - return FALSE; + if (!service->url->user) { + camel_exception_setv(ex, CAMEL_EXCEPTION_INVALID_PARAM, + _("Authentication requested but not username provided")); + return -1; + } /* if nessecary, prompt for the password */ if (!service->url->passwd) { - CamelException ex; char *prompt; prompt = g_strdup_printf (_("Please enter the NNTP password for %s@%s"), service->url->user, service->url->host); - - camel_exception_init (&ex); - service->url->passwd = camel_session_get_password (session, service, NULL, - prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, &ex); - camel_exception_clear (&ex); + prompt, "password", CAMEL_SESSION_PASSWORD_SECRET, ex); g_free (prompt); if (!service->url->passwd) - return FALSE; + return -1; } /* now, send auth info (currently, only authinfo user/pass is supported) */ - ret = camel_nntp_command(store, &line, "authinfo user %s", service->url->user); - if (ret == NNTP_AUTH_ACCEPTED) { - return TRUE; - } else if (ret == NNTP_AUTH_CONTINUE) { - ret = camel_nntp_command (store, &line, "authinfo pass %s", service->url->passwd); - if (ret == NNTP_AUTH_ACCEPTED) - return TRUE; - else - return FALSE; - } else - return FALSE; -} - -static gboolean -nntp_connected (CamelNNTPStore *store, CamelException *ex) -{ - if (((CamelDiscoStore *)store)->status == CAMEL_DISCO_STORE_OFFLINE) { - g_warning("Trying to talk to nntp session whilst offline"); - return FALSE; + ret = camel_nntp_raw_command(store, ex, &line, "authinfo user %s", service->url->user); + if (ret == NNTP_AUTH_CONTINUE) + ret = camel_nntp_raw_command(store, ex, &line, "authinfo pass %s", service->url->passwd); + + if (ret != NNTP_AUTH_ACCEPTED) { + if (ret != -1) + camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_CANT_AUTHENTICATE, + _("Cannot authenticate to server: %s"), line); + return -1; } - if (store->stream == NULL) - return camel_service_connect (CAMEL_SERVICE (store), ex); - - return TRUE; + return ret; } /* Enter owning lock */ int -camel_nntp_command (CamelNNTPStore *store, char **line, const char *fmt, ...) +camel_nntp_raw_commandv (CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, va_list ap) { const unsigned char *p, *ps; unsigned char c; - va_list ap; char *s; int d; unsigned int u, u2; e_mutex_assert_locked(store->priv->command_lock); - - if (!nntp_connected (store, NULL)) - return -1; - - /* Check for unprocessed data, ! */ - if (store->stream->mode == CAMEL_NNTP_STREAM_DATA) { - g_warning("Unprocessed data left in stream, flushing"); - while (camel_nntp_stream_getd(store->stream, (unsigned char **)&p, &u) > 0) - ; - } + g_assert(store->stream->mode != CAMEL_NNTP_STREAM_DATA); + camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE); - command_begin_send: - va_start(ap, fmt); ps = p = fmt; while ((c = *p++)) { switch (c) { @@ -1102,42 +1203,127 @@ camel_nntp_command (CamelNNTPStore *store, char **line, const char *fmt, ...) dd(printf("NNTP_COMMAND: '%.*s'\n", (int)store->mem->buffer->len, store->mem->buffer->data)); camel_stream_write ((CamelStream *) store->mem, "\r\n", 2); - if (camel_stream_write ((CamelStream *) store->stream, store->mem->buffer->data, store->mem->buffer->len) == -1 && errno != EINTR) { - camel_stream_reset ((CamelStream *) store->mem); - /* FIXME: hack */ - g_byte_array_set_size (store->mem->buffer, 0); - - reconnect: - /* some error, re-connect */ - camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL); - - if (!nntp_connected (store, NULL)) - return -1; - - goto command_begin_send; - } - - camel_stream_reset ((CamelStream *) store->mem); + if (camel_stream_write((CamelStream *) store->stream, store->mem->buffer->data, store->mem->buffer->len) == -1) + goto ioerror; + /* FIXME: hack */ + camel_stream_reset ((CamelStream *) store->mem); g_byte_array_set_size (store->mem->buffer, 0); if (camel_nntp_stream_line (store->stream, (unsigned char **) line, &u) == -1) - return -1; + goto ioerror; u = strtoul (*line, NULL, 10); - /* Check for 'authentication required' codes */ - if (u == NNTP_AUTH_REQUIRED && - camel_nntp_try_authenticate(store)) - goto command_begin_send; - - /* the server doesn't like us anymore, but we still like her! */ - if (u == 401 || u == 503) - goto reconnect; - /* Handle all switching to data mode here, to make callers job easier */ if (u == 215 || (u >= 220 && u <=224) || (u >= 230 && u <= 231)) camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_DATA); return u; + +ioerror: + if (errno == EINTR) + camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled.")); + else + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("NNTP Command failed: %s"), g_strerror(errno)); + return -1; +} + +int +camel_nntp_raw_command(CamelNNTPStore *store, CamelException *ex, char **line, const char *fmt, ...) +{ + int ret; + va_list ap; + + va_start(ap, fmt); + ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap); + va_end(ap); + + return ret; +} + +int +camel_nntp_command (CamelNNTPStore *store, CamelException *ex, CamelNNTPFolder *folder, char **line, const char *fmt, ...) +{ + const unsigned char *p; + va_list ap; + int ret, retry; + unsigned int u; + + e_mutex_assert_locked(store->priv->command_lock); + + if (((CamelDiscoStore *)store)->status == CAMEL_DISCO_STORE_OFFLINE) { + camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_NOT_CONNECTED, + _("Not connected.")); + return -1; + } + + /* Check for unprocessed data, ! */ + if (store->stream->mode == CAMEL_NNTP_STREAM_DATA) { + g_warning("Unprocessed data left in stream, flushing"); + while (camel_nntp_stream_getd(store->stream, (unsigned char **)&p, &u) > 0) + ; + } + camel_nntp_stream_set_mode(store->stream, CAMEL_NNTP_STREAM_LINE); + + retry = 0; + do { + retry ++; + + if (store->stream == NULL + && !camel_service_connect (CAMEL_SERVICE (store), ex)) + return -1; + + if (folder != NULL + && (store->current_folder == NULL || strcmp(store->current_folder, ((CamelFolder *)folder)->full_name) != 0)) { + ret = camel_nntp_raw_command(store, ex, line, "group %s", ((CamelFolder *)folder)->full_name); + if (ret == 211) { + g_free(store->current_folder); + store->current_folder = g_strdup(((CamelFolder *)folder)->full_name); + camel_nntp_folder_selected(folder, *line, ex); + if (camel_exception_is_set(ex)) + return -1; + } else { + goto error; + } + } + + /* dummy fmt, we just wanted to select the folder */ + if (fmt == NULL) + return 0; + + va_start(ap, fmt); + ret = camel_nntp_raw_commandv(store, ex, line, fmt, ap); + va_end(ap); + error: + switch (ret) { + case NNTP_AUTH_REQUIRED: + if (camel_nntp_try_authenticate(store, ex) != NNTP_AUTH_ACCEPTED) + return -1; + continue; + case 411: /* no such group */ + camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID, + _("No such folder: %s"), line); + return -1; + case 400: /* service discontinued */ + case 401: /* wrong client state - this should quit but this is what the old code did */ + case 503: /* information not available - this should quit but this is what the old code did (?) */ + camel_service_disconnect (CAMEL_SERVICE (store), FALSE, NULL); + continue; + case -1: /* i/o error */ + if (camel_exception_get_id(ex) == CAMEL_EXCEPTION_USER_CANCEL) + return -1; + camel_exception_clear(ex); + break; + } + } while (ret == -1 && retry < 3); + + if (ret == -1) { + if (errno == EINTR) + camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Cancelled.")); + else + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("NNTP Command failed: %s"), g_strerror(errno)); + } + + return ret; } diff --git a/camel/providers/nntp/camel-nntp-store.h b/camel/providers/nntp/camel-nntp-store.h index 10a56dcc89..0e68f4ae57 100644 --- a/camel/providers/nntp/camel-nntp-store.h +++ b/camel/providers/nntp/camel-nntp-store.h @@ -30,18 +30,14 @@ extern "C" { #pragma } #endif /* __cplusplus */ -#include <camel/camel-store.h> -#include <camel/camel-stream-mem.h> -#include <camel/camel-data-cache.h> -#include <camel/camel-exception.h> -#include <camel/camel-folder.h> - #include <camel/camel-disco-store.h> -#include <camel/camel-disco-folder.h> #include "camel-nntp-stream.h" #include "camel-nntp-store-summary.h" +struct _CamelNNTPFolder; +struct _CamelException; + #define CAMEL_NNTP_STORE_TYPE (camel_nntp_store_get_type ()) #define CAMEL_NNTP_STORE(obj) (CAMEL_CHECK_CAST((obj), CAMEL_NNTP_STORE_TYPE, CamelNNTPStore)) #define CAMEL_NNTP_STORE_CLASS(k) (CAMEL_CHECK_CLASS_CAST ((k), CAMEL_NNTP_STORE_TYPE, CamelNNTPStoreClass)) @@ -59,6 +55,20 @@ extern "C" { typedef struct _CamelNNTPStore CamelNNTPStore; typedef struct _CamelNNTPStoreClass CamelNNTPStoreClass; +enum _xover_t { + XOVER_STRING = 0, + XOVER_MSGID, + XOVER_SIZE, +}; + +struct _xover_header { + struct _xover_header *next; + + const char *name; + unsigned int skip:8; + enum _xover_t type:8; +}; + struct _CamelNNTPStore { CamelDiscoStore parent_object; @@ -66,17 +76,20 @@ struct _CamelNNTPStore { guint32 extensions; - gboolean posting_allowed; - gboolean do_short_folder_notation, folder_hierarchy_relative; + unsigned int posting_allowed:1; + unsigned int do_short_folder_notation:1; + unsigned int folder_hierarchy_relative:1; - CamelNNTPStoreSummary *summary; + struct _CamelNNTPStoreSummary *summary; - CamelNNTPStream *stream; - CamelStreamMem *mem; + struct _CamelNNTPStream *stream; + struct _CamelStreamMem *mem; - CamelDataCache *cache; + struct _CamelDataCache *cache; char *current_folder, *storage_path, *base_url; + + struct _xover_header *xover; }; struct _CamelNNTPStoreClass { @@ -87,8 +100,9 @@ struct _CamelNNTPStoreClass { /* Standard Camel function */ CamelType camel_nntp_store_get_type (void); -int camel_nntp_command(CamelNNTPStore *store, char **line, const char *fmt, ...); -int camel_nntp_store_set_folder(CamelNNTPStore *store, CamelFolder *folder, CamelFolderChangeInfo *changes, CamelException *ex); +int camel_nntp_raw_commandv (CamelNNTPStore *store, struct _CamelException *ex, char **line, const char *fmt, va_list ap); +int camel_nntp_raw_command(CamelNNTPStore *store, struct _CamelException *ex, char **line, const char *fmt, ...); +int camel_nntp_command (CamelNNTPStore *store, struct _CamelException *ex, struct _CamelNNTPFolder *folder, char **line, const char *fmt, ...); #ifdef __cplusplus } diff --git a/camel/providers/nntp/camel-nntp-summary.c b/camel/providers/nntp/camel-nntp-summary.c index d897db24e9..d30fdc6267 100644 --- a/camel/providers/nntp/camel-nntp-summary.c +++ b/camel/providers/nntp/camel-nntp-summary.c @@ -50,24 +50,6 @@ extern int camel_verbose_debug; #define CAMEL_NNTP_SUMMARY_VERSION (1) -static int xover_setup(CamelNNTPSummary *cns, CamelException *ex); -static int add_range_xover(CamelNNTPSummary *cns, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex); -static int add_range_head(CamelNNTPSummary *cns, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex); - -enum _xover_t { - XOVER_STRING = 0, - XOVER_MSGID, - XOVER_SIZE, -}; - -struct _xover_header { - struct _xover_header *next; - - const char *name; - unsigned int skip:8; - enum _xover_t type:8; -}; - struct _CamelNNTPSummaryPrivate { char *uid; @@ -136,29 +118,16 @@ static void camel_nntp_summary_finalise(CamelObject *obj) { CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(obj); - struct _xover_header *xover, *xn; - - xover = cns->priv->xover; - while (xover) { - xn = xover->next; - g_free(xover); - xover = xn; - } g_free(cns->priv); } CamelNNTPSummary * -camel_nntp_summary_new(CamelNNTPFolder *folder) +camel_nntp_summary_new(const char *path) { CamelNNTPSummary *cns = (CamelNNTPSummary *)camel_object_new(camel_nntp_summary_get_type()); - char *path; - cns->folder = folder; - path = g_strdup_printf ("%s%s", folder->storage_path, ".ev-summary"); camel_folder_summary_set_filename((CamelFolderSummary *)cns, path); - g_free(path); - camel_folder_summary_set_build_content((CamelFolderSummary *)cns, FALSE); return cns; @@ -230,198 +199,12 @@ summary_header_save(CamelFolderSummary *s, FILE *out) return 0; } -/* Assumes we have the stream */ -int -camel_nntp_summary_check(CamelNNTPSummary *cns, CamelFolderChangeInfo *changes, CamelException *ex) -{ - CamelNNTPStore *store; - CamelFolder *folder; - CamelFolderSummary *s; - int ret, i; - char *line; - unsigned int n, f, l; - int count; - - folder = (CamelFolder *)cns->folder; - store = (CamelNNTPStore *)folder->parent_store; - - if (((CamelDiscoStore *)store)->status == CAMEL_DISCO_STORE_OFFLINE) - return 0; - - if (xover_setup (cns, ex) == -1) { - camel_exception_setv (ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Connection error: %s"), strerror(errno)); - return -1; - } - - s = (CamelFolderSummary *)cns; - - ret = camel_nntp_command(store, &line, "group %s", folder->full_name); - if (ret == 411) { - camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID, - _("No such folder: %s"), line); - return -1; - } else if (ret != 211) { - if (ret < 0) - line = ""; - camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not get group: %s"), line); - return -1; - } - - line +=3; - n = strtoul(line, &line, 10); - f = strtoul(line, &line, 10); - l = strtoul(line, &line, 10); - - if (cns->low == f && cns->high == l) { - dd(printf("nntp_summary: no work to do!\n")); - return 0; - } - - /* Need to work out what to do with our messages */ - - /* Check for messages no longer on the server */ - if (cns->low != f) { - count = camel_folder_summary_count(s); - for (i = 0; i < count; i++) { - CamelMessageInfo *mi = camel_folder_summary_index(s, i); - - if (mi) { - const char *uid = camel_message_info_uid(mi); - const char *msgid; - - n = strtoul(uid, NULL, 10); - if (n < f || n > l) { - dd(printf("nntp_summary: %u is lower/higher than lowest/highest article, removed\n", n)); - /* Since we use a global cache this could prematurely remove - a cached message that might be in another folder - not that important as - it is a true cache */ - msgid = strchr(uid, ','); - if (msgid) - camel_data_cache_remove(store->cache, "cache", msgid+1, NULL); - camel_folder_change_info_remove_uid(changes, uid); - camel_folder_summary_remove(s, mi); - count--; - i--; - } - - camel_folder_summary_info_free(s, mi); - } - } - cns->low = f; - } - - if (cns->high < l) { - if (cns->high < f) - cns->high = f-1; - - if (cns->priv->xover) { - ret = add_range_xover(cns, l, cns->high+1, changes, ex); - } else { - ret = add_range_head(cns, l, cns->high+1, changes, ex); - } - } - - - /* TODO: not from here */ - camel_folder_summary_touch(s); - camel_folder_summary_save(s); - - if (ret < 0) - camel_exception_setv(ex, CAMEL_EXCEPTION_SERVICE_UNAVAILABLE, - _("Could not get messages: unspecified error")); - - return ret; -} - -static struct { - const char *name; - int type; -} headers[] = { - { "subject", 0 }, - { "from", 0 }, - { "date", 0 }, - { "message-id", 1 }, - { "references", 0 }, - { "bytes", 2 }, -}; +/* ********************************************************************** */ +/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */ static int -xover_setup(CamelNNTPSummary *cns, CamelException *ex) +add_range_xover(CamelNNTPSummary *cns, CamelNNTPStore *store, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex) { - CamelNNTPStore *store; - CamelFolder *folder; - CamelFolderSummary *s; - int ret, i; - char *line; - unsigned int len; - unsigned char c, *p; - struct _xover_header *xover, *last; - - if (cns->priv->xover_setup) - return 0; - - /* manual override */ - if (getenv("CAMEL_NNTP_DISABLE_XOVER") != NULL) { - cns->priv->xover_setup = TRUE; - return 0; - } - - folder = (CamelFolder *)cns->folder; - store = (CamelNNTPStore *)folder->parent_store; - s = (CamelFolderSummary *)cns; - - ret = camel_nntp_command(store, &line, "list overview.fmt"); - if (ret == -1) { - camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, - _("NNTP Command failed: %s"), strerror(errno)); - return -1; - } - - cns->priv->xover_setup = TRUE; - - /* unsupported command? */ - if (ret != 215) - return 0; - - last = (struct _xover_header *)&cns->priv->xover; - - /* supported command */ - while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) { - p = line; - xover = g_malloc0(sizeof(*xover)); - last->next = xover; - last = xover; - while ((c = *p++)) { - if (c == ':') { - p[-1] = 0; - for (i=0;i<sizeof(headers)/sizeof(headers[0]);i++) { - if (strcmp(line, headers[i].name) == 0) { - xover->name = headers[i].name; - if (strncmp(p, "full", 4) == 0) - xover->skip = strlen(xover->name)+1; - else - xover->skip = 0; - xover->type = headers[i].type; - break; - } - } - break; - } else { - p[-1] = tolower(c); - } - } - } - - return ret; -} - -static int -add_range_xover(CamelNNTPSummary *cns, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex) -{ - CamelNNTPStore *store; - CamelFolder *folder; CamelFolderSummary *s; CamelMessageInfo *mi; struct _camel_header_raw *headers = NULL; @@ -429,21 +212,20 @@ add_range_xover(CamelNNTPSummary *cns, unsigned int high, unsigned int low, Came int len, ret; unsigned int n, count, total, size; struct _xover_header *xover; - time_t last, now; - folder = (CamelFolder *)cns->folder; - store = (CamelNNTPStore *)folder->parent_store; s = (CamelFolderSummary *)cns; camel_operation_start(NULL, _("%s: Scanning new messages"), ((CamelService *)store)->url->host); - ret = camel_nntp_command(store, &line, "xover %r", low, high); + ret = camel_nntp_raw_command(store, ex, &line, "xover %r", low, high); if (ret != 224) { camel_operation_end(NULL); + if (ret != -1) + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, + _("Unexpected server response from xover: %s"), line); return -1; } - last = time(0); count = 0; total = high-low+1; while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) { @@ -453,7 +235,7 @@ add_range_xover(CamelNNTPSummary *cns, unsigned int high, unsigned int low, Came if (*tab != '\t') continue; tab++; - xover = cns->priv->xover; + xover = store->xover; size = 0; for (;tab[0] && xover;xover = xover->next) { line = tab; @@ -507,13 +289,6 @@ add_range_xover(CamelNNTPSummary *cns, unsigned int high, unsigned int low, Came } camel_header_raw_clear(&headers); - - now = time(0); - if (last + 2 < now) { - camel_object_trigger_event((CamelObject *)folder, "folder_changed", changes); - camel_folder_change_info_clear(changes); - last = now; - } } camel_operation_end(NULL); @@ -521,41 +296,36 @@ add_range_xover(CamelNNTPSummary *cns, unsigned int high, unsigned int low, Came return ret; } +/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */ static int -add_range_head(CamelNNTPSummary *cns, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex) +add_range_head(CamelNNTPSummary *cns, CamelNNTPStore *store, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex) { - CamelNNTPStore *store; - CamelFolder *folder; CamelFolderSummary *s; int i, ret = -1; char *line, *msgid; unsigned int n, count, total; CamelMessageInfo *mi; CamelMimeParser *mp; - time_t now, last; - folder = (CamelFolder *)cns->folder; - store = (CamelNNTPStore *)folder->parent_store; s = (CamelFolderSummary *)cns; mp = camel_mime_parser_new(); camel_operation_start(NULL, _("%s: Scanning new messages"), ((CamelService *)store)->url->host); - last = time(0); count = 0; total = high-low+1; for (i=low;i<high+1;i++) { camel_operation_progress(NULL, (count * 100) / total); count++; - ret = camel_nntp_command(store, &line, "head %u", i); + ret = camel_nntp_raw_command(store, ex, &line, "head %u", i); /* unknown article, ignore */ if (ret == 423) continue; else if (ret == -1) - goto error; + goto ioerror; else if (ret != 221) { - camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Unknown server response: %s"), line); + camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Unexpected server response from head: %s"), line); goto ioerror; } line += 3; @@ -588,13 +358,6 @@ add_range_head(CamelNNTPSummary *cns, unsigned int high, unsigned int low, Camel cns->priv->uid = NULL; } } - - now = time(0); - if (last + 2 < now) { - camel_object_trigger_event((CamelObject *)folder, "folder_changed", changes); - camel_folder_change_info_clear(changes); - last = now; - } } ret = 0; @@ -618,3 +381,124 @@ ioerror: return ret; } + +/* Assumes we have the stream */ +/* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */ +int +camel_nntp_summary_check(CamelNNTPSummary *cns, CamelNNTPStore *store, char *line, CamelFolderChangeInfo *changes, CamelException *ex) +{ + CamelFolderSummary *s; + int ret = 0, i; + unsigned int n, f, l; + int count; + char *folder = NULL; + CamelNNTPStoreInfo *si; + + s = (CamelFolderSummary *)cns; + + line +=3; + n = strtoul(line, &line, 10); + f = strtoul(line, &line, 10); + l = strtoul(line, &line, 10); + if (line[0] == ' ') { + char *tmp; + + folder = line+1; + tmp = strchr(folder, ' '); + if (tmp) + *tmp = 0; + tmp = g_alloca(strlen(folder)+1); + strcpy(tmp, folder); + folder = tmp; + } + + if (cns->low == f && cns->high == l) { + dd(printf("nntp_summary: no work to do!\n")); + goto update; + } + + /* Need to work out what to do with our messages */ + + /* Check for messages no longer on the server */ + if (cns->low != f) { + count = camel_folder_summary_count(s); + for (i = 0; i < count; i++) { + CamelMessageInfo *mi = camel_folder_summary_index(s, i); + + if (mi) { + const char *uid = camel_message_info_uid(mi); + const char *msgid; + + n = strtoul(uid, NULL, 10); + if (n < f || n > l) { + dd(printf("nntp_summary: %u is lower/higher than lowest/highest article, removed\n", n)); + /* Since we use a global cache this could prematurely remove + a cached message that might be in another folder - not that important as + it is a true cache */ + msgid = strchr(uid, ','); + if (msgid) + camel_data_cache_remove(store->cache, "cache", msgid+1, NULL); + camel_folder_change_info_remove_uid(changes, uid); + camel_folder_summary_remove(s, mi); + count--; + i--; + } + + camel_folder_summary_info_free(s, mi); + } + } + cns->low = f; + } + + if (cns->high < l) { + if (cns->high < f) + cns->high = f-1; + + if (store->xover) { + ret = add_range_xover(cns, store, l, cns->high+1, changes, ex); + } else { + ret = add_range_head(cns, store, l, cns->high+1, changes, ex); + } + } + + /* TODO: not from here */ + camel_folder_summary_touch(s); + camel_folder_summary_save(s); +update: + /* update store summary if we have it */ + if (folder + && (si = (CamelNNTPStoreInfo *)camel_store_summary_path((CamelStoreSummary *)store->summary, folder))) { + int unread = 0; + + count = camel_folder_summary_count(s); + for (i = 0; i < count; i++) { + CamelMessageInfo *mi = camel_folder_summary_index(s, i); + + if (mi) { + if ((mi->flags & CAMEL_MESSAGE_SEEN) == 0) + unread++; + camel_folder_summary_info_free(s, mi); + } + } + + if (si->info.unread != unread + || si->info.total != count + || si->first != f + || si->last != l) { + si->info.unread = unread; + si->info.total = count; + si->first = f; + si->last = l; + camel_store_summary_touch((CamelStoreSummary *)store->summary); + camel_store_summary_save((CamelStoreSummary *)store->summary); + } + camel_store_summary_info_free ((CamelStoreSummary *)store->summary, (CamelStoreInfo *)si); + } else { + if (folder) + g_warning("Group '%s' not present in summary", folder); + else + g_warning("Missing group from group response"); + } + + return ret; +} diff --git a/camel/providers/nntp/camel-nntp-summary.h b/camel/providers/nntp/camel-nntp-summary.h index b89ffe9209..fb58cea75a 100644 --- a/camel/providers/nntp/camel-nntp-summary.h +++ b/camel/providers/nntp/camel-nntp-summary.h @@ -22,8 +22,10 @@ #define _CAMEL_NNTP_SUMMARY_H #include <camel/camel-folder-summary.h> -#include <camel/camel-folder.h> -#include <camel/camel-exception.h> + +struct _CamelNNTPStore; +struct _CamelFolderChangeInfo; +struct _CamelException; #define CAMEL_NNTP_SUMMARY(obj) CAMEL_CHECK_CAST (obj, camel_nntp_summary_get_type (), CamelNNTPSummary) #define CAMEL_NNTP_SUMMARY_CLASS(klass) CAMEL_CHECK_CLASS_CAST (klass, camel_nntp_summary_get_type (), CamelNNTPSummaryClass) @@ -37,8 +39,6 @@ struct _CamelNNTPSummary { struct _CamelNNTPSummaryPrivate *priv; - struct _CamelNNTPFolder *folder; - guint32 version; guint32 high, low; }; @@ -48,20 +48,9 @@ struct _CamelNNTPSummaryClass { }; CamelType camel_nntp_summary_get_type (void); -CamelNNTPSummary *camel_nntp_summary_new(struct _CamelNNTPFolder *folder); - -int camel_nntp_summary_check(CamelNNTPSummary *cns, CamelFolderChangeInfo *, CamelException *ex); +CamelNNTPSummary *camel_nntp_summary_new(const char *path); -#if 0 -/* load/check the summary */ -int camel_nntp_summary_load(CamelNNTPSummary *cls, CamelException *ex); -/* check for new/removed messages */ -int camel_nntp_summary_check(CamelNNTPSummary *cls, CamelFolderChangeInfo *, CamelException *ex); -/* perform a folder sync or expunge, if needed */ -int camel_nntp_summary_sync(CamelNNTPSummary *cls, gboolean expunge, CamelFolderChangeInfo *, CamelException *ex); -/* add a new message to the summary */ -CamelMessageInfo *camel_nntp_summary_add(CamelNNTPSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *, CamelException *ex); -#endif +int camel_nntp_summary_check(CamelNNTPSummary *cns, struct _CamelNNTPStore *store, char *line, struct _CamelFolderChangeInfo *changes, struct _CamelException *ex); #endif /* ! _CAMEL_NNTP_SUMMARY_H */ |