aboutsummaryrefslogtreecommitdiffstats
path: root/camel/providers
diff options
context:
space:
mode:
authorNot Zed <NotZed@Ximian.com>2004-06-03 17:29:08 +0800
committerMichael Zucci <zucchi@src.gnome.org>2004-06-03 17:29:08 +0800
commit0a7400e900f9235cf6ef73a8978433178a5b8f0c (patch)
tree53f1ce1f63eff31afa5e3ef0c607a9af806cd441 /camel/providers
parentf60455997beaa2374f20921d2e874f70d08b4bb4 (diff)
downloadgsoc2013-evolution-0a7400e900f9235cf6ef73a8978433178a5b8f0c.tar.gz
gsoc2013-evolution-0a7400e900f9235cf6ef73a8978433178a5b8f0c.tar.zst
gsoc2013-evolution-0a7400e900f9235cf6ef73a8978433178a5b8f0c.zip
only save the summary, don't update from server, thats what refresh info
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. svn path=/trunk/; revision=26164
Diffstat (limited to 'camel/providers')
-rw-r--r--camel/providers/nntp/camel-nntp-folder.c262
-rw-r--r--camel/providers/nntp/camel-nntp-folder.h2
-rw-r--r--camel/providers/nntp/camel-nntp-grouplist.c3
-rw-r--r--camel/providers/nntp/camel-nntp-store-summary.c18
-rw-r--r--camel/providers/nntp/camel-nntp-store-summary.h2
-rw-r--r--camel/providers/nntp/camel-nntp-store.c494
-rw-r--r--camel/providers/nntp/camel-nntp-store.h44
-rw-r--r--camel/providers/nntp/camel-nntp-summary.c386
-rw-r--r--camel/providers/nntp/camel-nntp-summary.h23
9 files changed, 603 insertions, 631 deletions
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 */