1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
3 * Copyright (C) 2000 Ximian Inc.
5 * Authors: Michael Zucchi <notzed@ximian.com>
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of version 2 of the GNU Lesser General Public
9 * License as published by the Free Software Foundation.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this program; if not, write to the
18 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 * Boston, MA 02110-1301, USA.
33 #include <glib/gi18n-lib.h>
35 #include "camel/camel-data-cache.h"
36 #include "camel/camel-debug.h"
37 #include "camel/camel-file-utils.h"
38 #include "camel/camel-mime-message.h"
39 #include "camel/camel-operation.h"
40 #include "camel/camel-stream-null.h"
42 #include "camel-nntp-folder.h"
43 #include "camel-nntp-store.h"
44 #include "camel-nntp-stream.h"
45 #include "camel-nntp-summary.h"
49 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
50 #define dd(x) (camel_debug("nntp")?(x):0)
52 #define CAMEL_NNTP_SUMMARY_VERSION (1)
54 struct _CamelNNTPSummaryPrivate {
57 struct _xover_header *xover; /* xoverview format */
61 #define _PRIVATE(o) (((CamelNNTPSummary *)(o))->priv)
63 static CamelMessageInfo * message_info_new_from_header (CamelFolderSummary *, struct _camel_header_raw *);
64 static int summary_header_load(CamelFolderSummary *, FILE *);
65 static int summary_header_save(CamelFolderSummary *, FILE *);
67 static void camel_nntp_summary_class_init (CamelNNTPSummaryClass *klass);
68 static void camel_nntp_summary_init (CamelNNTPSummary *obj);
69 static void camel_nntp_summary_finalise (CamelObject *obj);
70 static CamelFolderSummaryClass *camel_nntp_summary_parent;
73 camel_nntp_summary_get_type(void)
75 static CamelType type = CAMEL_INVALID_TYPE;
77 if (type == CAMEL_INVALID_TYPE) {
78 type = camel_type_register(camel_folder_summary_get_type(), "CamelNNTPSummary",
79 sizeof (CamelNNTPSummary),
80 sizeof (CamelNNTPSummaryClass),
81 (CamelObjectClassInitFunc) camel_nntp_summary_class_init,
83 (CamelObjectInitFunc) camel_nntp_summary_init,
84 (CamelObjectFinalizeFunc) camel_nntp_summary_finalise);
91 camel_nntp_summary_class_init(CamelNNTPSummaryClass *klass)
93 CamelFolderSummaryClass *sklass = (CamelFolderSummaryClass *) klass;
95 camel_nntp_summary_parent = CAMEL_FOLDER_SUMMARY_CLASS(camel_type_get_global_classfuncs(camel_folder_summary_get_type()));
97 sklass->message_info_new_from_header = message_info_new_from_header;
98 sklass->summary_header_load = summary_header_load;
99 sklass->summary_header_save = summary_header_save;
103 camel_nntp_summary_init(CamelNNTPSummary *obj)
105 struct _CamelNNTPSummaryPrivate *p;
106 struct _CamelFolderSummary *s = (CamelFolderSummary *)obj;
108 p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
110 /* subclasses need to set the right instance data sizes */
111 s->message_info_size = sizeof(CamelMessageInfoBase);
112 s->content_info_size = sizeof(CamelMessageContentInfo);
114 /* and a unique file version */
115 s->version += CAMEL_NNTP_SUMMARY_VERSION;
119 camel_nntp_summary_finalise(CamelObject *obj)
121 CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(obj);
127 camel_nntp_summary_new(struct _CamelFolder *folder, const char *path)
129 CamelNNTPSummary *cns = (CamelNNTPSummary *)camel_object_new(camel_nntp_summary_get_type());
131 ((CamelFolderSummary *)cns)->folder = folder;
133 camel_folder_summary_set_filename((CamelFolderSummary *)cns, path);
134 camel_folder_summary_set_build_content((CamelFolderSummary *)cns, FALSE);
139 static CamelMessageInfo *
140 message_info_new_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
142 CamelMessageInfoBase *mi;
143 CamelNNTPSummary *cns = (CamelNNTPSummary *)s;
145 /* error to call without this setup */
146 if (cns->priv->uid == NULL)
149 /* we shouldn't be here if we already have this uid */
150 g_assert(camel_folder_summary_uid(s, cns->priv->uid) == NULL);
152 mi = (CamelMessageInfoBase *)((CamelFolderSummaryClass *)camel_nntp_summary_parent)->message_info_new_from_header(s, h);
155 mi->uid = cns->priv->uid;
156 cns->priv->uid = NULL;
159 return (CamelMessageInfo *)mi;
163 summary_header_load(CamelFolderSummary *s, FILE *in)
165 CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(s);
167 if (((CamelFolderSummaryClass *)camel_nntp_summary_parent)->summary_header_load(s, in) == -1)
171 if (s->version == 0x20c) {
172 camel_file_util_decode_fixed_int32(in, &cns->high);
173 return camel_file_util_decode_fixed_int32(in, &cns->low);
176 if (camel_file_util_decode_fixed_int32(in, &cns->version) == -1)
179 if (cns->version > CAMEL_NNTP_SUMMARY_VERSION) {
180 g_warning("Unknown NNTP summary version");
185 if (camel_file_util_decode_fixed_int32(in, &cns->high) == -1
186 || camel_file_util_decode_fixed_int32(in, &cns->low) == -1)
193 summary_header_save(CamelFolderSummary *s, FILE *out)
195 CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(s);
197 if (((CamelFolderSummaryClass *)camel_nntp_summary_parent)->summary_header_save(s, out) == -1
198 || camel_file_util_encode_fixed_int32(out, CAMEL_NNTP_SUMMARY_VERSION) == -1
199 || camel_file_util_encode_fixed_int32(out, cns->high) == -1
200 || camel_file_util_encode_fixed_int32(out, cns->low) == -1)
206 /* ********************************************************************** */
208 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
210 add_range_xover(CamelNNTPSummary *cns, CamelNNTPStore *store, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex)
212 CamelFolderSummary *s;
213 CamelMessageInfoBase *mi;
214 struct _camel_header_raw *headers = NULL;
217 unsigned int n, count, total, size;
218 struct _xover_header *xover;
220 s = (CamelFolderSummary *)cns;
222 camel_operation_start(NULL, _("%s: Scanning new messages"), ((CamelService *)store)->url->host);
224 ret = camel_nntp_raw_command_auth(store, ex, &line, "xover %r", low, high);
226 camel_operation_end(NULL);
228 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
229 _("Unexpected server response from xover: %s"), line);
235 while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) {
236 camel_operation_progress(NULL, (count * 100) / total);
238 n = strtoul(line, &tab, 10);
242 xover = store->xover;
244 for (;tab[0] && xover;xover = xover->next) {
246 tab = strchr(line, '\t');
250 tab = line+strlen(line);
252 /* do we care about this column? */
256 camel_header_raw_append(&headers, xover->name, line, -1);
257 switch(xover->type) {
261 cns->priv->uid = g_strdup_printf("%u,%s", n, line);
264 size = strtoul(line, NULL, 10);
271 /* skip headers we don't care about, incase the server doesn't actually send some it said it would. */
272 while (xover && xover->name == NULL)
275 /* truncated line? ignore? */
277 mi = (CamelMessageInfoBase *)camel_folder_summary_uid(s, cns->priv->uid);
279 mi = (CamelMessageInfoBase *)camel_folder_summary_add_from_header(s, headers);
283 camel_folder_change_info_add_uid(changes, camel_message_info_uid(mi));
286 camel_message_info_free(mi);
290 if (cns->priv->uid) {
291 g_free(cns->priv->uid);
292 cns->priv->uid = NULL;
295 camel_header_raw_clear(&headers);
298 camel_operation_end(NULL);
303 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
305 add_range_head(CamelNNTPSummary *cns, CamelNNTPStore *store, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex)
307 CamelFolderSummary *s;
310 unsigned int i, n, count, total;
311 CamelMessageInfo *mi;
314 s = (CamelFolderSummary *)cns;
316 mp = camel_mime_parser_new();
318 camel_operation_start(NULL, _("%s: Scanning new messages"), ((CamelService *)store)->url->host);
322 for (i=low;i<high+1;i++) {
323 camel_operation_progress(NULL, (count * 100) / total);
325 ret = camel_nntp_raw_command_auth(store, ex, &line, "head %u", i);
326 /* unknown article, ignore */
331 else if (ret != 221) {
332 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Unexpected server response from head: %s"), line);
336 n = strtoul(line, &line, 10);
338 g_warning("retrieved message '%u' when i expected '%u'?\n", n, i);
340 /* FIXME: use camel-mime-utils.c function for parsing msgid? */
341 if ((msgid = strchr(line, '<')) && (line = strchr(msgid+1, '>'))){
343 cns->priv->uid = g_strdup_printf("%u,%s\n", n, msgid);
344 mi = camel_folder_summary_uid(s, cns->priv->uid);
346 if (camel_mime_parser_init_with_stream(mp, (CamelStream *)store->stream) == -1)
348 mi = camel_folder_summary_add_from_parser(s, mp);
349 while (camel_mime_parser_step(mp, NULL, NULL) != CAMEL_MIME_PARSER_STATE_EOF)
355 camel_folder_change_info_add_uid(changes, camel_message_info_uid(mi));
357 /* already have, ignore */
358 camel_message_info_free(mi);
360 if (cns->priv->uid) {
361 g_free(cns->priv->uid);
362 cns->priv->uid = NULL;
372 camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Use cancel"));
374 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Operation failed: %s"), strerror(errno));
378 if (cns->priv->uid) {
379 g_free(cns->priv->uid);
380 cns->priv->uid = NULL;
382 camel_object_unref((CamelObject *)mp);
384 camel_operation_end(NULL);
389 /* Assumes we have the stream */
390 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
392 camel_nntp_summary_check(CamelNNTPSummary *cns, CamelNNTPStore *store, char *line, CamelFolderChangeInfo *changes, CamelException *ex)
394 CamelFolderSummary *s;
396 unsigned int n, f, l;
399 CamelNNTPStoreInfo *si;
401 s = (CamelFolderSummary *)cns;
404 n = strtoul(line, &line, 10);
405 f = strtoul(line, &line, 10);
406 l = strtoul(line, &line, 10);
407 if (line[0] == ' ') {
411 tmp = strchr(folder, ' ');
414 tmp = g_alloca(strlen(folder)+1);
419 if (cns->low == f && cns->high == l) {
420 dd(printf("nntp_summary: no work to do!\n"));
424 /* Need to work out what to do with our messages */
426 /* Check for messages no longer on the server */
428 count = camel_folder_summary_count(s);
429 for (i = 0; i < count; i++) {
430 CamelMessageInfo *mi = camel_folder_summary_index(s, i);
433 const char *uid = camel_message_info_uid(mi);
436 n = strtoul(uid, NULL, 10);
437 if (n < f || n > l) {
438 dd(printf("nntp_summary: %u is lower/higher than lowest/highest article, removed\n", n));
439 /* Since we use a global cache this could prematurely remove
440 a cached message that might be in another folder - not that important as
441 it is a true cache */
442 msgid = strchr(uid, ',');
444 camel_data_cache_remove(store->cache, "cache", msgid+1, NULL);
445 camel_folder_change_info_remove_uid(changes, uid);
446 camel_folder_summary_remove(s, mi);
451 camel_message_info_free(mi);
462 ret = add_range_xover(cns, store, l, cns->high+1, changes, ex);
464 ret = add_range_head(cns, store, l, cns->high+1, changes, ex);
468 /* TODO: not from here */
469 camel_folder_summary_touch(s);
470 camel_folder_summary_save(s);
472 /* update store summary if we have it */
474 && (si = (CamelNNTPStoreInfo *)camel_store_summary_path((CamelStoreSummary *)store->summary, folder))) {
477 count = camel_folder_summary_count(s);
478 for (i = 0; i < count; i++) {
479 CamelMessageInfoBase *mi = (CamelMessageInfoBase *)camel_folder_summary_index(s, i);
482 if ((mi->flags & CAMEL_MESSAGE_SEEN) == 0)
484 camel_message_info_free(mi);
488 if (si->info.unread != unread
489 || si->info.total != count
492 si->info.unread = unread;
493 si->info.total = count;
496 camel_store_summary_touch((CamelStoreSummary *)store->summary);
497 camel_store_summary_save((CamelStoreSummary *)store->summary);
499 camel_store_summary_info_free ((CamelStoreSummary *)store->summary, (CamelStoreInfo *)si);
502 g_warning("Group '%s' not present in summary", folder);
504 g_warning("Missing group from group response");