aboutsummaryrefslogtreecommitdiffstats
path: root/camel/providers/nntp/camel-nntp-summary.c
diff options
context:
space:
mode:
Diffstat (limited to 'camel/providers/nntp/camel-nntp-summary.c')
-rw-r--r--camel/providers/nntp/camel-nntp-summary.c581
1 files changed, 581 insertions, 0 deletions
diff --git a/camel/providers/nntp/camel-nntp-summary.c b/camel/providers/nntp/camel-nntp-summary.c
new file mode 100644
index 0000000000..9f6ff5f095
--- /dev/null
+++ b/camel/providers/nntp/camel-nntp-summary.c
@@ -0,0 +1,581 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
+/*
+ * Copyright (C) 2000 Ximian Inc.
+ *
+ * Authors: Michael Zucchi <notzed@ximian.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ctype.h>
+#include <sys/stat.h>
+#include <sys/uio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include "camel/camel-file-utils.h"
+#include "camel/camel-mime-message.h"
+#include "camel/camel-stream-null.h"
+#include "camel/camel-operation.h"
+#include "camel/camel-data-cache.h"
+
+#include "camel-nntp-summary.h"
+#include "camel-nntp-folder.h"
+#include "camel-nntp-store.h"
+#include "camel-nntp-stream.h"
+
+#define w(x)
+#define io(x)
+#define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
+extern int camel_verbose_debug;
+#define dd(x) (camel_verbose_debug?(x):0)
+
+#define CAMEL_NNTP_SUMMARY_VERSION (0x200)
+
+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;
+
+ struct _xover_header *xover; /* xoverview format */
+ int xover_setup;
+};
+
+#define _PRIVATE(o) (((CamelNNTPSummary *)(o))->priv)
+
+static CamelMessageInfo * message_info_new (CamelFolderSummary *, struct _header_raw *);
+static int summary_header_load(CamelFolderSummary *, FILE *);
+static int summary_header_save(CamelFolderSummary *, FILE *);
+
+static void camel_nntp_summary_class_init (CamelNNTPSummaryClass *klass);
+static void camel_nntp_summary_init (CamelNNTPSummary *obj);
+static void camel_nntp_summary_finalise (CamelObject *obj);
+static CamelFolderSummaryClass *camel_nntp_summary_parent;
+
+CamelType
+camel_nntp_summary_get_type(void)
+{
+ static CamelType type = CAMEL_INVALID_TYPE;
+
+ if (type == CAMEL_INVALID_TYPE) {
+ type = camel_type_register(camel_folder_summary_get_type(), "CamelNNTPSummary",
+ sizeof (CamelNNTPSummary),
+ sizeof (CamelNNTPSummaryClass),
+ (CamelObjectClassInitFunc) camel_nntp_summary_class_init,
+ NULL,
+ (CamelObjectInitFunc) camel_nntp_summary_init,
+ (CamelObjectFinalizeFunc) camel_nntp_summary_finalise);
+ }
+
+ return type;
+}
+
+static void
+camel_nntp_summary_class_init(CamelNNTPSummaryClass *klass)
+{
+ CamelFolderSummaryClass *sklass = (CamelFolderSummaryClass *) klass;
+
+ camel_nntp_summary_parent = CAMEL_FOLDER_SUMMARY_CLASS(camel_type_get_global_classfuncs(camel_folder_summary_get_type()));
+
+ sklass->message_info_new = message_info_new;
+ sklass->summary_header_load = summary_header_load;
+ sklass->summary_header_save = summary_header_save;
+}
+
+static void
+camel_nntp_summary_init(CamelNNTPSummary *obj)
+{
+ struct _CamelNNTPSummaryPrivate *p;
+ struct _CamelFolderSummary *s = (CamelFolderSummary *)obj;
+
+ p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
+
+ /* subclasses need to set the right instance data sizes */
+ s->message_info_size = sizeof(CamelMessageInfo);
+ s->content_info_size = sizeof(CamelMessageContentInfo);
+
+ /* and a unique file version */
+ s->version += CAMEL_NNTP_SUMMARY_VERSION;
+}
+
+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)
+{
+ CamelNNTPSummary *cns = (CamelNNTPSummary *)camel_object_new(camel_nntp_summary_get_type());
+ char *path;
+
+ cns->folder = folder;
+ path = g_strdup_printf("%s.ev-summary", folder->storage_path);
+ camel_folder_summary_set_filename((CamelFolderSummary *)cns, path);
+ g_free(path);
+
+ camel_folder_summary_set_build_content((CamelFolderSummary *)cns, FALSE);
+
+ return cns;
+}
+
+static CamelMessageInfo *
+message_info_new(CamelFolderSummary *s, struct _header_raw *h)
+{
+ CamelMessageInfo *mi;
+ CamelNNTPSummary *cns = (CamelNNTPSummary *)s;
+
+ /* error to call without this setup */
+ if (cns->priv->uid == NULL)
+ return NULL;
+
+ /* we shouldn't be here if we already have this uid */
+ g_assert(camel_folder_summary_uid(s, cns->priv->uid) == NULL);
+
+ mi = ((CamelFolderSummaryClass *)camel_nntp_summary_parent)->message_info_new(s, h);
+ if (mi) {
+ camel_message_info_set_uid(mi, cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+
+ return mi;
+}
+
+static int summary_header_load(CamelFolderSummary *s, FILE *in)
+{
+ CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(s);
+
+ if (((CamelFolderSummaryClass *)camel_nntp_summary_parent)->summary_header_load(s, in) == -1
+ || camel_file_util_decode_fixed_int32(in, &cns->high) == -1
+ || camel_file_util_decode_fixed_int32(in, &cns->low) == -1)
+ return -1;
+
+ return 0;
+}
+
+static int summary_header_save(CamelFolderSummary *s, FILE *out)
+{
+ CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(s);
+
+ if (((CamelFolderSummaryClass *)camel_nntp_summary_parent)->summary_header_save(s, out) == -1
+ || camel_file_util_encode_fixed_int32(out, cns->high) == -1
+ || camel_file_util_encode_fixed_int32(out, cns->low) == -1)
+ return -1;
+
+ 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;
+
+ if (xover_setup(cns, ex) == -1)
+ return -1;
+
+ folder = (CamelFolder *)cns->folder;
+ store = (CamelNNTPStore *)folder->parent_store;
+ 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) {
+ 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);
+
+ dd(printf("nntp_summary: got last '%u' first '%u'\n"
+ "nntp_summary: high '%u' low '%u'\n", l, f, cns->high, cns->low));
+
+ 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);
+ }
+ }
+
+ camel_folder_summary_touch(s);
+
+ return ret;
+}
+
+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(CamelNNTPSummary *cns, 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 _header_raw *headers = NULL;
+ char *line, *tab;
+ 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);
+ if (ret != 224) {
+ camel_operation_end(NULL);
+ return -1;
+ }
+
+ last = time(0);
+ count = 0;
+ total = high-low+1;
+ while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) {
+ camel_operation_progress(NULL, (count * 100) / total);
+ count++;
+ n = strtoul(line, &tab, 10);
+ if (*tab != '\t')
+ continue;
+ tab++;
+ xover = cns->priv->xover;
+ size = 0;
+ for (;tab[0] && xover;xover = xover->next) {
+ line = tab;
+ tab = strchr(line, '\t');
+ if (tab)
+ *tab++ = 0;
+ else
+ tab = line+strlen(line);
+
+ /* do we care about this column? */
+ if (xover->name) {
+ line += xover->skip;
+ if (line < tab) {
+ header_raw_append(&headers, xover->name, line, -1);
+ switch(xover->type) {
+ case XOVER_STRING:
+ break;
+ case XOVER_MSGID:
+ cns->priv->uid = g_strdup_printf("%u,%s", n, line);
+ break;
+ case XOVER_SIZE:
+ size = strtoul(line, NULL, 10);
+ break;
+ }
+ }
+ }
+ }
+
+ /* truncated line? ignore? */
+ if (xover == NULL) {
+ mi = camel_folder_summary_uid(s, cns->priv->uid);
+ if (mi == NULL) {
+ mi = camel_folder_summary_add_from_header(s, headers);
+ if (mi) {
+ mi->size = size;
+ cns->high = n;
+ camel_folder_change_info_add_uid(changes, camel_message_info_uid(mi));
+ }
+ } else {
+ camel_folder_summary_info_free(s, mi);
+ }
+ }
+
+ if (cns->priv->uid) {
+ g_free(cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+
+ 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);
+
+ return ret;
+}
+
+static int
+add_range_head(CamelNNTPSummary *cns, 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);
+ /* unknown article, ignore */
+ if (ret == 423)
+ continue;
+ else if (ret == -1)
+ goto error;
+ else if (ret != 221) {
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Unknown server response: %s"), line);
+ goto ioerror;
+ }
+ line += 3;
+ n = strtoul(line, &line, 10);
+ if (n != i)
+ g_warning("retrieved message '%d' when i expected '%d'?\n", n, i);
+
+ if ((msgid = strchr(line, '<')) && (line = strchr(msgid+1, '>'))){
+ line[1] = 0;
+ cns->priv->uid = g_strdup_printf("%u,%s\n", n, msgid);
+ mi = camel_folder_summary_uid(s, cns->priv->uid);
+ if (mi == NULL) {
+ if (camel_mime_parser_init_with_stream(mp, (CamelStream *)store->stream) == -1)
+ goto error;
+ mi = camel_folder_summary_add_from_parser(s, mp);
+ while (camel_mime_parser_step(mp, NULL, NULL) != HSCAN_EOF)
+ ;
+ if (mi == NULL) {
+ goto error;
+ }
+ cns->high = i;
+ camel_folder_change_info_add_uid(changes, camel_message_info_uid(mi));
+ } else {
+ /* already have, ignore */
+ camel_folder_summary_info_free(s, mi);
+ }
+ if (cns->priv->uid) {
+ g_free(cns->priv->uid);
+ 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;
+error:
+
+ if (ret == -1) {
+ if (errno == EINTR)
+ camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Use cancel"));
+ else
+ camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Operation failed: %s"), strerror(errno));
+ }
+ioerror:
+
+ if (cns->priv->uid) {
+ g_free(cns->priv->uid);
+ cns->priv->uid = NULL;
+ }
+ camel_object_unref((CamelObject *)mp);
+
+ camel_operation_end(NULL);
+
+ return ret;
+}