Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / nntp / camel-nntp-summary.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*- */
2 /*
3  *  Copyright (C) 2000 Ximian Inc.
4  *
5  *  Authors: Michael Zucchi <notzed@ximian.com>
6  *
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.
10  *
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.
15  *
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.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <ctype.h>
27 #include <errno.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <unistd.h>
31 #include <sys/stat.h>
32
33 #include <glib/gi18n-lib.h>
34
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"
41
42 #include "camel-nntp-folder.h"
43 #include "camel-nntp-store.h"
44 #include "camel-nntp-stream.h"
45 #include "camel-nntp-summary.h"
46
47 #define w(x)
48 #define io(x)
49 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
50 #define dd(x) (camel_debug("nntp")?(x):0)
51
52 #define CAMEL_NNTP_SUMMARY_VERSION (1)
53
54 struct _CamelNNTPSummaryPrivate {
55         char *uid;
56
57         struct _xover_header *xover; /* xoverview format */
58         int xover_setup;
59 };
60
61 #define _PRIVATE(o) (((CamelNNTPSummary *)(o))->priv)
62
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 *);
66
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;
71
72 CamelType
73 camel_nntp_summary_get_type(void)
74 {
75         static CamelType type = CAMEL_INVALID_TYPE;
76         
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,
82                                            NULL,
83                                            (CamelObjectInitFunc) camel_nntp_summary_init,
84                                            (CamelObjectFinalizeFunc) camel_nntp_summary_finalise);
85         }
86         
87         return type;
88 }
89
90 static void
91 camel_nntp_summary_class_init(CamelNNTPSummaryClass *klass)
92 {
93         CamelFolderSummaryClass *sklass = (CamelFolderSummaryClass *) klass;
94         
95         camel_nntp_summary_parent = CAMEL_FOLDER_SUMMARY_CLASS(camel_type_get_global_classfuncs(camel_folder_summary_get_type()));
96
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;
100 }
101
102 static void
103 camel_nntp_summary_init(CamelNNTPSummary *obj)
104 {
105         struct _CamelNNTPSummaryPrivate *p;
106         struct _CamelFolderSummary *s = (CamelFolderSummary *)obj;
107
108         p = _PRIVATE(obj) = g_malloc0(sizeof(*p));
109
110         /* subclasses need to set the right instance data sizes */
111         s->message_info_size = sizeof(CamelMessageInfoBase);
112         s->content_info_size = sizeof(CamelMessageContentInfo);
113
114         /* and a unique file version */
115         s->version += CAMEL_NNTP_SUMMARY_VERSION;
116 }
117
118 static void
119 camel_nntp_summary_finalise(CamelObject *obj)
120 {
121         CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(obj);
122
123         g_free(cns->priv);
124 }
125
126 CamelNNTPSummary *
127 camel_nntp_summary_new(struct _CamelFolder *folder, const char *path)
128 {
129         CamelNNTPSummary *cns = (CamelNNTPSummary *)camel_object_new(camel_nntp_summary_get_type());
130
131         ((CamelFolderSummary *)cns)->folder = folder;
132
133         camel_folder_summary_set_filename((CamelFolderSummary *)cns, path);
134         camel_folder_summary_set_build_content((CamelFolderSummary *)cns, FALSE);
135         
136         return cns;
137 }
138
139 static CamelMessageInfo *
140 message_info_new_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
141 {
142         CamelMessageInfoBase *mi;
143         CamelNNTPSummary *cns = (CamelNNTPSummary *)s;
144
145         /* error to call without this setup */
146         if (cns->priv->uid == NULL)
147                 return NULL;
148
149         /* we shouldn't be here if we already have this uid */
150         g_assert(camel_folder_summary_uid(s, cns->priv->uid) == NULL);
151
152         mi = (CamelMessageInfoBase *)((CamelFolderSummaryClass *)camel_nntp_summary_parent)->message_info_new_from_header(s, h);
153         if (mi) {
154                 g_free(mi->uid);
155                 mi->uid = cns->priv->uid;
156                 cns->priv->uid = NULL;
157         }
158         
159         return (CamelMessageInfo *)mi;
160 }
161
162 static int
163 summary_header_load(CamelFolderSummary *s, FILE *in)
164 {
165         CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(s);
166
167         if (((CamelFolderSummaryClass *)camel_nntp_summary_parent)->summary_header_load(s, in) == -1)
168                 return -1;
169
170         /* Legacy version */
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);
174         }
175
176         if (camel_file_util_decode_fixed_int32(in, &cns->version) == -1)
177                 return -1;
178
179         if (cns->version > CAMEL_NNTP_SUMMARY_VERSION) {
180                 g_warning("Unknown NNTP summary version");
181                 errno = EINVAL;
182                 return -1;
183         }
184
185         if (camel_file_util_decode_fixed_int32(in, &cns->high) == -1
186             || camel_file_util_decode_fixed_int32(in, &cns->low) == -1)
187                 return -1;
188
189         return 0;
190 }
191
192 static int
193 summary_header_save(CamelFolderSummary *s, FILE *out)
194 {
195         CamelNNTPSummary *cns = CAMEL_NNTP_SUMMARY(s);
196
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)
201                 return -1;
202
203         return 0;
204 }
205
206 /* ********************************************************************** */
207
208 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
209 static int
210 add_range_xover(CamelNNTPSummary *cns, CamelNNTPStore *store, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex)
211 {
212         CamelFolderSummary *s;
213         CamelMessageInfoBase *mi;
214         struct _camel_header_raw *headers = NULL;
215         char *line, *tab;
216         int len, ret;
217         unsigned int n, count, total, size;
218         struct _xover_header *xover;
219
220         s = (CamelFolderSummary *)cns;
221
222         camel_operation_start(NULL, _("%s: Scanning new messages"), ((CamelService *)store)->url->host);
223
224         ret = camel_nntp_raw_command_auth(store, ex, &line, "xover %r", low, high);
225         if (ret != 224) {
226                 camel_operation_end(NULL);
227                 if (ret != -1)
228                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
229                                              _("Unexpected server response from xover: %s"), line);
230                 return -1;
231         }
232
233         count = 0;
234         total = high-low+1;
235         while ((ret = camel_nntp_stream_line(store->stream, (unsigned char **)&line, &len)) > 0) {
236                 camel_operation_progress(NULL, (count * 100) / total);
237                 count++;
238                 n = strtoul(line, &tab, 10);
239                 if (*tab != '\t')
240                         continue;
241                 tab++;
242                 xover = store->xover;
243                 size = 0;
244                 for (;tab[0] && xover;xover = xover->next) {
245                         line = tab;
246                         tab = strchr(line, '\t');
247                         if (tab)
248                                 *tab++ = 0;
249                         else
250                                 tab = line+strlen(line);
251
252                         /* do we care about this column? */
253                         if (xover->name) {
254                                 line += xover->skip;
255                                 if (line < tab) {
256                                         camel_header_raw_append(&headers, xover->name, line, -1);
257                                         switch(xover->type) {
258                                         case XOVER_STRING:
259                                                 break;
260                                         case XOVER_MSGID:
261                                                 cns->priv->uid = g_strdup_printf("%u,%s", n, line);
262                                                 break;
263                                         case XOVER_SIZE:
264                                                 size = strtoul(line, NULL, 10);
265                                                 break;
266                                         }
267                                 }
268                         }
269                 }
270
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)
273                         xover = xover->next;
274
275                 /* truncated line? ignore? */
276                 if (xover == NULL) {
277                         mi = (CamelMessageInfoBase *)camel_folder_summary_uid(s, cns->priv->uid);
278                         if (mi == NULL) {
279                                 mi = (CamelMessageInfoBase *)camel_folder_summary_add_from_header(s, headers);
280                                 if (mi) {
281                                         mi->size = size;
282                                         cns->high = n;
283                                         camel_folder_change_info_add_uid(changes, camel_message_info_uid(mi));
284                                 }
285                         } else {
286                                 camel_message_info_free(mi);
287                         }
288                 }
289
290                 if (cns->priv->uid) {
291                         g_free(cns->priv->uid);
292                         cns->priv->uid = NULL;
293                 }
294
295                 camel_header_raw_clear(&headers);
296         }
297
298         camel_operation_end(NULL);
299
300         return ret;
301 }
302
303 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
304 static int
305 add_range_head(CamelNNTPSummary *cns, CamelNNTPStore *store, unsigned int high, unsigned int low, CamelFolderChangeInfo *changes, CamelException *ex)
306 {
307         CamelFolderSummary *s;
308         int ret = -1;
309         char *line, *msgid;
310         unsigned int i, n, count, total;
311         CamelMessageInfo *mi;
312         CamelMimeParser *mp;
313
314         s = (CamelFolderSummary *)cns;
315
316         mp = camel_mime_parser_new();
317
318         camel_operation_start(NULL, _("%s: Scanning new messages"), ((CamelService *)store)->url->host);
319
320         count = 0;
321         total = high-low+1;
322         for (i=low;i<high+1;i++) {
323                 camel_operation_progress(NULL, (count * 100) / total);
324                 count++;
325                 ret = camel_nntp_raw_command_auth(store, ex, &line, "head %u", i);
326                 /* unknown article, ignore */
327                 if (ret == 423)
328                         continue;
329                 else if (ret == -1)
330                         goto ioerror;
331                 else if (ret != 221) {
332                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Unexpected server response from head: %s"), line);
333                         goto ioerror;
334                 }
335                 line += 3;
336                 n = strtoul(line, &line, 10);
337                 if (n != i)
338                         g_warning("retrieved message '%u' when i expected '%u'?\n", n, i);
339                 
340                 /* FIXME: use camel-mime-utils.c function for parsing msgid? */
341                 if ((msgid = strchr(line, '<')) && (line = strchr(msgid+1, '>'))){
342                         line[1] = 0;
343                         cns->priv->uid = g_strdup_printf("%u,%s\n", n, msgid);
344                         mi = camel_folder_summary_uid(s, cns->priv->uid);
345                         if (mi == NULL) {
346                                 if (camel_mime_parser_init_with_stream(mp, (CamelStream *)store->stream) == -1)
347                                         goto error;
348                                 mi = camel_folder_summary_add_from_parser(s, mp);
349                                 while (camel_mime_parser_step(mp, NULL, NULL) != CAMEL_MIME_PARSER_STATE_EOF)
350                                         ;
351                                 if (mi == NULL) {
352                                         goto error;
353                                 }
354                                 cns->high = i;
355                                 camel_folder_change_info_add_uid(changes, camel_message_info_uid(mi));
356                         } else {
357                                 /* already have, ignore */
358                                 camel_message_info_free(mi);
359                         }
360                         if (cns->priv->uid) {
361                                 g_free(cns->priv->uid);
362                                 cns->priv->uid = NULL;
363                         }
364                 }
365         }
366
367         ret = 0;
368 error:
369
370         if (ret == -1) {
371                 if (errno == EINTR)
372                         camel_exception_setv(ex, CAMEL_EXCEPTION_USER_CANCEL, _("Use cancel"));
373                 else
374                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Operation failed: %s"), strerror(errno));
375         }
376 ioerror:
377
378         if (cns->priv->uid) {
379                 g_free(cns->priv->uid);
380                 cns->priv->uid = NULL;
381         }
382         camel_object_unref((CamelObject *)mp);
383
384         camel_operation_end(NULL);
385
386         return ret;
387 }
388
389 /* Assumes we have the stream */
390 /* Note: This will be called from camel_nntp_command, so only use camel_nntp_raw_command */
391 int
392 camel_nntp_summary_check(CamelNNTPSummary *cns, CamelNNTPStore *store, char *line, CamelFolderChangeInfo *changes, CamelException *ex)
393 {
394         CamelFolderSummary *s;
395         int ret = 0, i;
396         unsigned int n, f, l;
397         int count;
398         char *folder = NULL;
399         CamelNNTPStoreInfo *si;
400
401         s = (CamelFolderSummary *)cns;
402
403         line +=3;
404         n = strtoul(line, &line, 10);
405         f = strtoul(line, &line, 10);
406         l = strtoul(line, &line, 10);
407         if (line[0] == ' ') {
408                 char *tmp;
409
410                 folder = line+1;
411                 tmp = strchr(folder, ' ');
412                 if (tmp)
413                         *tmp = 0;
414                 tmp = g_alloca(strlen(folder)+1);
415                 strcpy(tmp, folder);
416                 folder = tmp;
417         }
418
419         if (cns->low == f && cns->high == l) {
420                 dd(printf("nntp_summary: no work to do!\n"));
421                 goto update;
422         }
423
424         /* Need to work out what to do with our messages */
425
426         /* Check for messages no longer on the server */
427         if (cns->low != f) {
428                 count = camel_folder_summary_count(s);
429                 for (i = 0; i < count; i++) {
430                         CamelMessageInfo *mi = camel_folder_summary_index(s, i);
431
432                         if (mi) {
433                                 const char *uid = camel_message_info_uid(mi);
434                                 const char *msgid;
435
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, ',');
443                                         if (msgid)
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);
447                                         count--;
448                                         i--;
449                                 }
450                                 
451                                 camel_message_info_free(mi);
452                         }
453                 }
454                 cns->low = f;
455         }
456
457         if (cns->high < l) {
458                 if (cns->high < f)
459                         cns->high = f-1;
460
461                 if (store->xover) {
462                         ret = add_range_xover(cns, store, l, cns->high+1, changes, ex);
463                 } else {
464                         ret = add_range_head(cns, store, l, cns->high+1, changes, ex);
465                 }
466         }
467
468         /* TODO: not from here */
469         camel_folder_summary_touch(s);
470         camel_folder_summary_save(s);
471 update:
472         /* update store summary if we have it */
473         if (folder
474             && (si = (CamelNNTPStoreInfo *)camel_store_summary_path((CamelStoreSummary *)store->summary, folder))) {
475                 int unread = 0;
476
477                 count = camel_folder_summary_count(s);
478                 for (i = 0; i < count; i++) {
479                         CamelMessageInfoBase *mi = (CamelMessageInfoBase *)camel_folder_summary_index(s, i);
480
481                         if (mi) {
482                                 if ((mi->flags & CAMEL_MESSAGE_SEEN) == 0)
483                                         unread++;
484                                 camel_message_info_free(mi);
485                         }
486                 }
487                 
488                 if (si->info.unread != unread
489                     || si->info.total != count
490                     || si->first != f
491                     || si->last != l) {
492                         si->info.unread = unread;
493                         si->info.total = count;
494                         si->first = f;
495                         si->last = l;
496                         camel_store_summary_touch((CamelStoreSummary *)store->summary);
497                         camel_store_summary_save((CamelStoreSummary *)store->summary);
498                 }
499                 camel_store_summary_info_free ((CamelStoreSummary *)store->summary, (CamelStoreInfo *)si);
500         } else {
501                 if (folder)
502                         g_warning("Group '%s' not present in summary", folder);
503                 else
504                         g_warning("Missing group from group response");
505         }
506
507         return ret;
508 }