Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / local / camel-mbox-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 <fcntl.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <unistd.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34
35 #include <glib.h>
36 #include <glib/gi18n-lib.h>
37 #include <glib/gstdio.h>
38
39 #include "camel-file-utils.h"
40 #include "camel-mime-message.h"
41 #include "camel-operation.h"
42 #include "camel-private.h"
43
44 #include "camel-mbox-summary.h"
45
46 #define io(x)
47 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
48
49 #define CAMEL_MBOX_SUMMARY_VERSION (1)
50
51 static int summary_header_load (CamelFolderSummary *, FILE *);
52 static int summary_header_save (CamelFolderSummary *, FILE *);
53
54 static CamelMessageInfo * message_info_new_from_header(CamelFolderSummary *, struct _camel_header_raw *);
55 static CamelMessageInfo * message_info_new_from_parser(CamelFolderSummary *, CamelMimeParser *);
56 static CamelMessageInfo * message_info_load (CamelFolderSummary *, FILE *);
57 static int                message_info_save (CamelFolderSummary *, FILE *, CamelMessageInfo *);
58 static int                meta_message_info_save(CamelFolderSummary *s, FILE *out_meta, FILE *out, CamelMessageInfo *mi);
59 /*static void             message_info_free (CamelFolderSummary *, CamelMessageInfo *);*/
60
61 static char *mbox_summary_encode_x_evolution (CamelLocalSummary *cls, const CamelLocalMessageInfo *mi);
62
63 static int mbox_summary_check(CamelLocalSummary *cls, CamelFolderChangeInfo *changeinfo, CamelException *ex);
64 static int mbox_summary_sync(CamelLocalSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex);
65 #ifdef STATUS_PINE
66 static CamelMessageInfo *mbox_summary_add(CamelLocalSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *ci, CamelException *ex);
67 #endif
68
69 static int mbox_summary_sync_quick(CamelMboxSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex);
70 static int mbox_summary_sync_full(CamelMboxSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex);
71
72 static void camel_mbox_summary_class_init (CamelMboxSummaryClass *klass);
73 static void camel_mbox_summary_init       (CamelMboxSummary *obj);
74 static void camel_mbox_summary_finalise   (CamelObject *obj);
75
76 #ifdef STATUS_PINE
77 /* Which status flags are stored in each separate header */
78 #define STATUS_XSTATUS (CAMEL_MESSAGE_FLAGGED|CAMEL_MESSAGE_ANSWERED|CAMEL_MESSAGE_DELETED)
79 #define STATUS_STATUS (CAMEL_MESSAGE_SEEN)
80
81 static void encode_status(guint32 flags, char status[8]);
82 static guint32 decode_status(const char *status);
83 #endif
84
85 static CamelLocalSummaryClass *camel_mbox_summary_parent;
86
87 CamelType
88 camel_mbox_summary_get_type(void)
89 {
90         static CamelType type = CAMEL_INVALID_TYPE;
91         
92         if (type == CAMEL_INVALID_TYPE) {
93                 type = camel_type_register(camel_local_summary_get_type(), "CamelMboxSummary",
94                                            sizeof (CamelMboxSummary),
95                                            sizeof (CamelMboxSummaryClass),
96                                            (CamelObjectClassInitFunc) camel_mbox_summary_class_init,
97                                            NULL,
98                                            (CamelObjectInitFunc) camel_mbox_summary_init,
99                                            (CamelObjectFinalizeFunc) camel_mbox_summary_finalise);
100         }
101         
102         return type;
103 }
104 static gboolean
105 mbox_info_set_user_flag(CamelMessageInfo *mi, const char *name, gboolean value)
106 {
107         int res;
108
109         res = ((CamelFolderSummaryClass *)camel_mbox_summary_parent)->info_set_user_flag(mi, name, value);
110         if (res)
111                 ((CamelLocalMessageInfo *)mi)->info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
112
113         return res;
114 }
115
116 static gboolean
117 mbox_info_set_user_tag(CamelMessageInfo *mi, const char *name, const char *value)
118 {
119         int res;
120
121         res = ((CamelFolderSummaryClass *)camel_mbox_summary_parent)->info_set_user_tag(mi, name, value);
122         if (res)
123                 ((CamelLocalMessageInfo *)mi)->info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED;
124
125         return res;
126 }
127
128 #ifdef STATUS_PINE
129 static gboolean
130 mbox_info_set_flags(CamelMessageInfo *mi, guint32 flags, guint32 set)
131 {
132         /* Basically, if anything could change the Status line, presume it does */
133         if (((CamelMboxSummary *)mi->summary)->xstatus
134             && (flags & (CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_FLAGGED|CAMEL_MESSAGE_ANSWERED|CAMEL_MESSAGE_DELETED))) {
135                 flags |= CAMEL_MESSAGE_FOLDER_XEVCHANGE|CAMEL_MESSAGE_FOLDER_FLAGGED;
136                 set |= CAMEL_MESSAGE_FOLDER_XEVCHANGE|CAMEL_MESSAGE_FOLDER_FLAGGED;
137         }
138
139         return ((CamelFolderSummaryClass *)camel_mbox_summary_parent)->info_set_flags(mi, flags, set);
140 }
141 #endif
142
143 static void
144 camel_mbox_summary_class_init(CamelMboxSummaryClass *klass)
145 {
146         CamelFolderSummaryClass *sklass = (CamelFolderSummaryClass *)klass;
147         CamelLocalSummaryClass *lklass = (CamelLocalSummaryClass *)klass;
148         
149         camel_mbox_summary_parent = (CamelLocalSummaryClass *)camel_type_get_global_classfuncs(camel_local_summary_get_type());
150
151         sklass->summary_header_load = summary_header_load;
152         sklass->summary_header_save = summary_header_save;
153
154         sklass->message_info_new_from_header  = message_info_new_from_header;
155         sklass->message_info_new_from_parser = message_info_new_from_parser;
156         sklass->message_info_load = message_info_load;
157         sklass->message_info_save = message_info_save;
158         sklass->meta_message_info_save = meta_message_info_save;
159         /*sklass->message_info_free = message_info_free;*/
160
161         sklass->info_set_user_flag = mbox_info_set_user_flag;
162         sklass->info_set_user_tag = mbox_info_set_user_tag;
163 #ifdef STATUS_PINE
164         sklass->info_set_flags = mbox_info_set_flags;
165 #endif
166         
167         lklass->encode_x_evolution = mbox_summary_encode_x_evolution;
168         lklass->check = mbox_summary_check;
169         lklass->sync = mbox_summary_sync;
170 #ifdef STATUS_PINE
171         lklass->add = mbox_summary_add;
172 #endif
173
174         klass->sync_quick = mbox_summary_sync_quick;
175         klass->sync_full = mbox_summary_sync_full;
176 }
177
178 static void
179 camel_mbox_summary_init(CamelMboxSummary *obj)
180 {
181         struct _CamelFolderSummary *s = (CamelFolderSummary *)obj;
182         
183         /* subclasses need to set the right instance data sizes */
184         s->message_info_size = sizeof(CamelMboxMessageInfo);
185         s->content_info_size = sizeof(CamelMboxMessageContentInfo);
186
187         /* and a unique file version */
188         s->version += CAMEL_MBOX_SUMMARY_VERSION;
189 }
190
191 static void
192 camel_mbox_summary_finalise(CamelObject *obj)
193 {
194         /*CamelMboxSummary *mbs = CAMEL_MBOX_SUMMARY(obj);*/
195 }
196
197 /**
198  * camel_mbox_summary_new:
199  *
200  * Create a new CamelMboxSummary object.
201  * 
202  * Return value: A new CamelMboxSummary widget.
203  **/
204 CamelMboxSummary *
205 camel_mbox_summary_new(struct _CamelFolder *folder, const char *filename, const char *mbox_name, CamelIndex *index)
206 {
207         CamelMboxSummary *new = (CamelMboxSummary *)camel_object_new(camel_mbox_summary_get_type());
208
209         ((CamelFolderSummary *)new)->folder = folder;
210
211         camel_local_summary_construct((CamelLocalSummary *)new, filename, mbox_name, index);
212         return new;
213 }
214
215 void camel_mbox_summary_xstatus(CamelMboxSummary *mbs, int state)
216 {
217         mbs->xstatus = state;
218 }
219
220 static char *
221 mbox_summary_encode_x_evolution (CamelLocalSummary *cls, const CamelLocalMessageInfo *mi)
222 {
223         const char *p, *uidstr;
224         guint32 uid;
225
226         /* This is busted, it is supposed to encode ALL DATA */
227         p = uidstr = camel_message_info_uid(mi);
228         while (*p && isdigit(*p))
229                 p++;
230         
231         if (*p == 0 && sscanf(uidstr, "%u", &uid) == 1) {
232                 return g_strdup_printf("%08x-%04x", uid, mi->info.flags & 0xffff);
233         } else {
234                 return g_strdup_printf("%s-%04x", uidstr, mi->info.flags & 0xffff);
235         }
236 }
237
238 static int
239 summary_header_load(CamelFolderSummary *s, FILE *in)
240 {
241         CamelMboxSummary *mbs = CAMEL_MBOX_SUMMARY(s);
242
243         if (((CamelFolderSummaryClass *)camel_mbox_summary_parent)->summary_header_load(s, in) == -1)
244                 return -1;
245
246         /* legacy version */
247         if (s->version == 0x120c)
248                 return camel_file_util_decode_uint32(in, (guint32 *) &mbs->folder_size);
249
250         /* version 1 */
251         if (camel_file_util_decode_fixed_int32(in, &mbs->version) == -1
252             || camel_file_util_decode_size_t(in, &mbs->folder_size) == -1)
253                 return -1;
254
255         return 0;
256 }
257
258 static int
259 summary_header_save(CamelFolderSummary *s, FILE *out)
260 {
261         CamelMboxSummary *mbs = CAMEL_MBOX_SUMMARY(s);
262
263         if (((CamelFolderSummaryClass *)camel_mbox_summary_parent)->summary_header_save(s, out) == -1)
264                 return -1;
265
266         camel_file_util_encode_fixed_int32(out, CAMEL_MBOX_SUMMARY_VERSION);
267
268         return camel_file_util_encode_size_t(out, mbs->folder_size);
269 }
270
271 static CamelMessageInfo *
272 message_info_new_from_header(CamelFolderSummary *s, struct _camel_header_raw *h)
273 {
274         CamelMboxMessageInfo *mi;
275         CamelMboxSummary *mbs = (CamelMboxSummary *)s;
276
277         mi = (CamelMboxMessageInfo *)((CamelFolderSummaryClass *)camel_mbox_summary_parent)->message_info_new_from_header(s, h);
278         if (mi) {
279                 const char *xev, *uid;
280                 CamelMboxMessageInfo *info = NULL;
281                 int add = 0;    /* bitmask of things to add, 1 assign uid, 2, just add as new, 4 = recent */
282 #ifdef STATUS_PINE
283                 const char *status = NULL, *xstatus = NULL;
284                 guint32 flags = 0;
285
286                 if (mbs->xstatus) {
287                         /* check for existance of status & x-status headers */
288                         status = camel_header_raw_find(&h, "Status", NULL);
289                         if (status)
290                                 flags = decode_status(status);
291                         xstatus = camel_header_raw_find(&h, "X-Status", NULL);
292                         if (xstatus)
293                                 flags |= decode_status(xstatus);
294                 }
295 #endif
296                 /* if we have an xev header, use it, else assign a new one */
297                 xev = camel_header_raw_find(&h, "X-Evolution", NULL);
298                 if (xev != NULL
299                     && camel_local_summary_decode_x_evolution((CamelLocalSummary *)s, xev, &mi->info) == 0) {
300                         uid = camel_message_info_uid(mi);
301                         d(printf("found valid x-evolution: %s\n", uid));
302                         info = (CamelMboxMessageInfo *)camel_folder_summary_uid(s, uid);
303                         if (info) {
304                                 if ((info->info.info.flags & CAMEL_MESSAGE_FOLDER_NOTSEEN)) {
305                                         info->info.info.flags &= ~CAMEL_MESSAGE_FOLDER_NOTSEEN;
306                                         camel_message_info_free(mi);
307                                         mi = info;
308                                 } else {
309                                         add = 7;
310                                         d(printf("seen '%s' before, adding anew\n", uid));
311                                         camel_message_info_free(info);
312                                 }
313                         } else {
314                                 add = 2;
315                                 d(printf("but isn't present in summary\n"));
316                         }
317                 } else {
318                         d(printf("didn't find x-evolution\n"));
319                         add = 7;
320                 }
321
322                 if (add&1) {
323                         mi->info.info.flags |= CAMEL_MESSAGE_FOLDER_FLAGGED | CAMEL_MESSAGE_FOLDER_NOXEV;
324                         g_free (mi->info.info.uid);
325                         mi->info.info.uid = camel_folder_summary_next_uid_string(s);
326                 } else {
327                         camel_folder_summary_set_uid(s, strtoul(camel_message_info_uid(mi), NULL, 10));
328                 }
329 #ifdef STATUS_PINE
330                 if (mbs->xstatus && add&2) {
331                         /* use the status as the flags when we read it the first time */
332                         if (status)
333                                 mi->info.info.flags = (mi->info.info.flags & ~(STATUS_STATUS)) | (flags & STATUS_STATUS);
334                         if (xstatus)
335                                 mi->info.info.flags = (mi->info.info.flags & ~(STATUS_XSTATUS)) | (flags & STATUS_XSTATUS);
336                 }
337 #endif
338                 if (mbs->changes) {
339                         if (add&2)
340                                 camel_folder_change_info_add_uid(mbs->changes, camel_message_info_uid(mi));
341                         if ((add&4) && status == NULL)
342                                 camel_folder_change_info_recent_uid(mbs->changes, camel_message_info_uid(mi));
343                 }
344
345                 mi->frompos = -1;
346         }
347         
348         return (CamelMessageInfo *)mi;
349 }
350
351 static CamelMessageInfo *
352 message_info_new_from_parser(CamelFolderSummary *s, CamelMimeParser *mp)
353 {
354         CamelMessageInfo *mi;
355
356         mi = ((CamelFolderSummaryClass *)camel_mbox_summary_parent)->message_info_new_from_parser(s, mp);
357         if (mi) {
358                 CamelMboxMessageInfo *mbi = (CamelMboxMessageInfo *)mi;
359
360                 mbi->frompos = camel_mime_parser_tell_start_from(mp);
361         }
362         
363         return mi;
364 }
365
366 static CamelMessageInfo *
367 message_info_load(CamelFolderSummary *s, FILE *in)
368 {
369         CamelMessageInfo *mi;
370
371         io(printf("loading mbox message info\n"));
372
373         mi = ((CamelFolderSummaryClass *)camel_mbox_summary_parent)->message_info_load(s, in);
374         if (mi) {
375                 CamelMboxMessageInfo *mbi = (CamelMboxMessageInfo *)mi;
376                 
377                 if (camel_file_util_decode_off_t(in, &mbi->frompos) == -1)
378                         goto error;
379         }
380         
381         return mi;
382 error:
383         camel_message_info_free(mi);
384         return NULL;
385 }
386
387 static int
388 meta_message_info_save(CamelFolderSummary *s, FILE *out_meta, FILE *out, CamelMessageInfo *mi)
389 {
390         CamelMboxMessageInfo *mbi = (CamelMboxMessageInfo *)mi;
391
392         io(printf("saving mbox message info\n"));
393
394         if (((CamelFolderSummaryClass *)camel_mbox_summary_parent)->meta_message_info_save(s, out_meta, out, mi) == -1
395             || camel_file_util_encode_off_t(out_meta, mbi->frompos) == -1)
396                 return -1;
397
398         return 0;
399 }
400
401 static int
402 message_info_save(CamelFolderSummary *s, FILE *out, CamelMessageInfo *mi)
403 {
404         CamelMboxMessageInfo *mbi = (CamelMboxMessageInfo *)mi;
405
406         io(printf("saving mbox message info\n"));
407
408         if (((CamelFolderSummaryClass *)camel_mbox_summary_parent)->message_info_save(s, out, mi) == -1
409             || camel_file_util_encode_off_t(out, mbi->frompos) == -1)
410                 return -1;
411
412         return 0;
413 }
414
415 /* like summary_rebuild, but also do changeinfo stuff (if supplied) */
416 static int
417 summary_update(CamelLocalSummary *cls, off_t offset, CamelFolderChangeInfo *changeinfo, CamelException *ex)
418 {
419         int i, count;
420         CamelFolderSummary *s = (CamelFolderSummary *)cls;
421         CamelMboxSummary *mbs = (CamelMboxSummary *)cls;
422         CamelMimeParser *mp;
423         CamelMboxMessageInfo *mi;
424         int fd;
425         int ok = 0;
426         struct stat st;
427         off_t size = 0;
428
429         d(printf("Calling summary update, from pos %d\n", (int)offset));
430
431         cls->index_force = FALSE;
432
433         camel_operation_start(NULL, _("Storing folder"));
434
435         fd = g_open(cls->folder_path, O_LARGEFILE | O_RDONLY | O_BINARY, 0);
436         if (fd == -1) {
437                 d(printf("%s failed to open: %s\n", cls->folder_path, strerror (errno)));
438                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
439                                       _("Could not open folder: %s: %s"),
440                                       cls->folder_path, g_strerror (errno));
441                 camel_operation_end(NULL);
442                 return -1;
443         }
444         
445         if (fstat(fd, &st) == 0)
446                 size = st.st_size;
447
448         mp = camel_mime_parser_new();
449         camel_mime_parser_init_with_fd(mp, fd);
450         camel_mime_parser_scan_from(mp, TRUE);
451         camel_mime_parser_seek(mp, offset, SEEK_SET);
452
453         if (offset > 0) {
454                 if (camel_mime_parser_step(mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM
455                     && camel_mime_parser_tell_start_from(mp) == offset) {
456                         camel_mime_parser_unstep(mp);
457                 } else {
458                         g_warning("The next message didn't start where I expected, building summary from start");
459                         camel_mime_parser_drop_step(mp);
460                         offset = 0;
461                         camel_mime_parser_seek(mp, offset, SEEK_SET);
462                 }
463         }
464
465         /* we mark messages as to whether we've seen them or not.
466            If we're not starting from the start, we must be starting
467            from the old end, so everything must be treated as new */
468         count = camel_folder_summary_count(s);
469         for (i=0;i<count;i++) {
470                 mi = (CamelMboxMessageInfo *)camel_folder_summary_index(s, i);
471                 if (offset == 0)
472                         mi->info.info.flags |= CAMEL_MESSAGE_FOLDER_NOTSEEN;
473                 else
474                         mi->info.info.flags &= ~CAMEL_MESSAGE_FOLDER_NOTSEEN;
475                 camel_message_info_free(mi);
476         }
477         mbs->changes = changeinfo;
478
479         while (camel_mime_parser_step(mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM) {
480                 CamelMessageInfo *info;
481                 off_t pc = camel_mime_parser_tell_start_from (mp) + 1;
482                 
483                 camel_operation_progress (NULL, (int) (((float) pc / size) * 100));
484
485                 info = camel_folder_summary_add_from_parser(s, mp);
486                 if (info == NULL) {
487                         camel_exception_setv(ex, 1, _("Fatal mail parser error near position %ld in folder %s"),
488                                              camel_mime_parser_tell(mp), cls->folder_path);
489                         ok = -1;
490                         break;
491                 }
492
493                 g_assert(camel_mime_parser_step(mp, NULL, NULL) == CAMEL_MIME_PARSER_STATE_FROM_END);
494         }
495
496         camel_object_unref(CAMEL_OBJECT (mp));
497
498         count = camel_folder_summary_count(s);
499         for (i=0;i<count;i++) {
500                 mi = (CamelMboxMessageInfo *)camel_folder_summary_index(s, i);
501                 /* must've dissapeared from the file? */
502                 if (mi->info.info.flags & CAMEL_MESSAGE_FOLDER_NOTSEEN) {
503                         d(printf("uid '%s' vanished, removing", camel_message_info_uid(mi)));
504                         if (changeinfo)
505                                 camel_folder_change_info_remove_uid(changeinfo, camel_message_info_uid(mi));
506                         camel_folder_summary_remove(s, (CamelMessageInfo *)mi);
507                         count--;
508                         i--;
509                 }
510                 camel_message_info_free(mi);
511         }
512         mbs->changes = NULL;
513         
514         /* update the file size/mtime in the summary */
515         if (ok != -1) {
516                 if (g_stat(cls->folder_path, &st) == 0) {
517                         camel_folder_summary_touch(s);
518                         mbs->folder_size = st.st_size;
519                         s->time = st.st_mtime;
520                 }
521         }
522
523         camel_operation_end(NULL);
524
525         return ok;
526 }
527
528 static int
529 mbox_summary_check(CamelLocalSummary *cls, CamelFolderChangeInfo *changes, CamelException *ex)
530 {
531         CamelMboxSummary *mbs = (CamelMboxSummary *)cls;
532         CamelFolderSummary *s = (CamelFolderSummary *)cls;
533         struct stat st;
534         int ret = 0;
535         int i, count;
536
537         d(printf("Checking summary\n"));
538
539         /* check if the summary is up-to-date */
540         if (g_stat(cls->folder_path, &st) == -1) {
541                 camel_folder_summary_clear(s);
542                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
543                                       _("Cannot check folder: %s: %s"),
544                                       cls->folder_path, g_strerror (errno));
545                 return -1;
546         }
547
548         if (cls->check_force)
549                 mbs->folder_size = 0;
550         cls->check_force = 0;
551
552         if (st.st_size == 0) {
553                 /* empty?  No need to scan at all */
554                 d(printf("Empty mbox, clearing summary\n"));
555                 count= camel_folder_summary_count(s);
556                 for (i=0;i<count;i++) {
557                         CamelMessageInfo *info = camel_folder_summary_index(s, i);
558
559                         if (info) {
560                                 camel_folder_change_info_remove_uid(changes, camel_message_info_uid(info));
561                                 camel_message_info_free(info);
562                         }
563                 }
564                 camel_folder_summary_clear(s);
565                 ret = 0;
566         } else {
567                 /* is the summary uptodate? */
568                 if (st.st_size != mbs->folder_size || st.st_mtime != s->time) {
569                         if (mbs->folder_size < st.st_size) {
570                                 /* this will automatically rescan from 0 if there is a problem */
571                                 d(printf("folder grew, attempting to rebuild from %d\n", mbs->folder_size));
572                                 ret = summary_update(cls, mbs->folder_size, changes, ex);
573                         } else {
574                                 d(printf("folder shrank!  rebuilding from start\n"));
575                                 ret = summary_update(cls, 0, changes, ex);
576                         }
577                 } else {
578                         d(printf("Folder unchanged, do nothing\n"));
579                 }
580         }
581
582         /* FIXME: move upstream? */
583
584         if (ret != -1) {
585                 if (mbs->folder_size != st.st_size || s->time != st.st_mtime) {
586                         mbs->folder_size = st.st_size;
587                         s->time = st.st_mtime;
588                         camel_folder_summary_touch(s);
589                 }
590         }
591
592         return ret;
593 }
594
595 /* perform a full sync */
596 static int
597 mbox_summary_sync_full(CamelMboxSummary *mbs, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex)
598 {
599         CamelLocalSummary *cls = (CamelLocalSummary *)mbs;
600         int fd = -1, fdout = -1;
601         char *tmpname = NULL;
602         guint32 flags = (expunge?1:0);
603
604         d(printf("performing full summary/sync\n"));
605
606         camel_operation_start(NULL, _("Storing folder"));
607
608         fd = g_open(cls->folder_path, O_LARGEFILE | O_RDONLY | O_BINARY, 0);
609         if (fd == -1) {
610                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
611                                       _("Could not open file: %s: %s"),
612                                       cls->folder_path, g_strerror (errno));
613                 camel_operation_end(NULL);
614                 return -1;
615         }
616
617         tmpname = g_alloca (strlen (cls->folder_path) + 5);
618         sprintf (tmpname, "%s.tmp", cls->folder_path);
619         d(printf("Writing temporary file to %s\n", tmpname));
620         fdout = g_open(tmpname, O_LARGEFILE|O_WRONLY|O_CREAT|O_TRUNC|O_BINARY, 0600);
621         if (fdout == -1) {
622                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
623                                       _("Cannot open temporary mailbox: %s"),
624                                       g_strerror (errno));
625                 goto error;
626         }
627
628         if (camel_mbox_summary_sync_mbox((CamelMboxSummary *)cls, flags, changeinfo, fd, fdout, ex) == -1)
629                 goto error;
630
631         d(printf("Closing folders\n"));
632
633         if (close(fd) == -1) {
634                 g_warning("Cannot close source folder: %s", strerror (errno));
635                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
636                                       _("Could not close source folder %s: %s"),
637                                       cls->folder_path, g_strerror (errno));
638                 fd = -1;
639                 goto error;
640         }
641
642         if (close(fdout) == -1) {
643                 g_warning("Cannot close temporary folder: %s", strerror (errno));
644                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
645                                       _("Could not close temporary folder: %s"),
646                                       g_strerror (errno));
647                 fdout = -1;
648                 goto error;
649         }
650
651         /* this should probably either use unlink/link/unlink, or recopy over
652            the original mailbox, for various locking reasons/etc */
653 #ifdef G_OS_WIN32
654         if (g_file_test(cls->folder_path,G_FILE_TEST_IS_REGULAR) && g_remove(cls->folder_path) == -1)
655                 g_warning ("Cannot remove %s: %s", cls->folder_path, g_strerror (errno));
656 #endif
657         if (g_rename(tmpname, cls->folder_path) == -1) {
658                 g_warning("Cannot rename folder: %s", strerror (errno));
659                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
660                                       _("Could not rename folder: %s"),
661                                       g_strerror (errno));
662                 goto error;
663         }
664         tmpname = NULL;
665
666         camel_operation_end(NULL);
667                 
668         return 0;
669  error:
670         if (fd != -1)
671                 close(fd);
672         
673         if (fdout != -1)
674                 close(fdout);
675         
676         if (tmpname)
677                 g_unlink(tmpname);
678
679         camel_operation_end(NULL);
680
681         return -1;
682 }
683
684 /* perform a quick sync - only system flags have changed */
685 static int
686 mbox_summary_sync_quick(CamelMboxSummary *mbs, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex)
687 {
688         CamelLocalSummary *cls = (CamelLocalSummary *)mbs;
689         CamelFolderSummary *s = (CamelFolderSummary *)mbs;
690         CamelMimeParser *mp = NULL;
691         int i, count;
692         CamelMboxMessageInfo *info = NULL;
693         int fd = -1, pfd;
694         char *xevnew, *xevtmp;
695         const char *xev;
696         int len;
697         off_t lastpos;
698
699         d(printf("Performing quick summary sync\n"));
700
701         camel_operation_start(NULL, _("Storing folder"));
702
703         fd = g_open(cls->folder_path, O_LARGEFILE|O_RDWR|O_BINARY, 0);
704         if (fd == -1) {
705                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
706                                       _("Could not open file: %s: %s"),
707                                       cls->folder_path, g_strerror (errno));
708
709                 camel_operation_end(NULL);
710                 return -1;
711         }
712
713         /* need to dup since mime parser closes its fd once it is finalised */
714         pfd = dup(fd);
715         if (pfd == -1) {
716                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
717                                      _("Could not store folder: %s"),
718                                      g_strerror(errno));
719                 close(fd);
720                 return -1;
721         }
722
723         mp = camel_mime_parser_new();
724         camel_mime_parser_scan_from(mp, TRUE);
725         camel_mime_parser_scan_pre_from(mp, TRUE);
726         camel_mime_parser_init_with_fd(mp, pfd);
727
728         count = camel_folder_summary_count(s);
729         for (i = 0; i < count; i++) {
730                 int xevoffset;
731                 int pc = (i+1)*100/count;
732
733                 camel_operation_progress(NULL, pc);
734
735                 info = (CamelMboxMessageInfo *)camel_folder_summary_index(s, i);
736
737                 g_assert(info);
738
739                 d(printf("Checking message %s %08x\n", camel_message_info_uid(info), info->info.flags));
740
741                 if ((info->info.info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED) == 0) {
742                         camel_message_info_free((CamelMessageInfo *)info);
743                         info = NULL;
744                         continue;
745                 }
746
747                 d(printf("Updating message %s\n", camel_message_info_uid(info)));
748
749                 camel_mime_parser_seek(mp, info->frompos, SEEK_SET);
750
751                 if (camel_mime_parser_step(mp, 0, 0) != CAMEL_MIME_PARSER_STATE_FROM) {
752                         g_warning("Expected a From line here, didn't get it");
753                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
754                                              _("Summary and folder mismatch, even after a sync"));
755                         goto error;
756                 }
757
758                 if (camel_mime_parser_tell_start_from(mp) != info->frompos) {
759                         g_warning("Didn't get the next message where I expected (%d) got %d instead",
760                                   (int)info->frompos, (int)camel_mime_parser_tell_start_from(mp));
761                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
762                                              _("Summary and folder mismatch, even after a sync"));
763                         goto error;
764                 }
765
766                 if (camel_mime_parser_step(mp, 0, 0) == CAMEL_MIME_PARSER_STATE_FROM_END) {
767                         g_warning("camel_mime_parser_step failed (2)");
768                         goto error;
769                 }
770
771                 xev = camel_mime_parser_header(mp, "X-Evolution", &xevoffset);
772                 if (xev == NULL || camel_local_summary_decode_x_evolution(cls, xev, NULL) == -1) {
773                         g_warning("We're supposed to have a valid x-ev header, but we dont");
774                         goto error;
775                 }
776                 xevnew = camel_local_summary_encode_x_evolution(cls, &info->info);
777                 /* SIGH: encode_param_list is about the only function which folds headers by itself.
778                    This should be fixed somehow differently (either parser doesn't fold headers,
779                    or param_list doesn't, or something */
780                 xevtmp = camel_header_unfold(xevnew);
781                 /* the raw header contains a leading ' ', so (dis)count that too */
782                 if (strlen(xev)-1 != strlen(xevtmp)) {
783                         g_free(xevnew);
784                         g_free(xevtmp);
785                         g_warning("Hmm, the xev headers shouldn't have changed size, but they did");
786                         goto error;
787                 }
788                 g_free(xevtmp);
789
790                 /* we write out the xevnew string, assuming its been folded identically to the original too! */
791
792                 lastpos = lseek(fd, 0, SEEK_CUR);
793                 lseek(fd, xevoffset+strlen("X-Evolution: "), SEEK_SET);
794                 do {
795                         len = write(fd, xevnew, strlen(xevnew));
796                 } while (len == -1 && errno == EINTR);
797                 lseek(fd, lastpos, SEEK_SET);
798                 g_free(xevnew);
799
800                 camel_mime_parser_drop_step(mp);
801                 camel_mime_parser_drop_step(mp);
802
803                 info->info.info.flags &= 0xffff;
804                 camel_message_info_free((CamelMessageInfo *)info);
805         }
806
807         d(printf("Closing folders\n"));
808
809         if (close(fd) == -1) {
810                 g_warning ("Cannot close source folder: %s", strerror (errno));
811                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
812                                       _("Could not close source folder %s: %s"),
813                                       cls->folder_path, g_strerror (errno));
814                 fd = -1;
815                 goto error;
816         }
817
818         camel_object_unref((CamelObject *)mp);
819
820         camel_operation_end(NULL);
821         
822         return 0;
823  error:
824         if (fd != -1)
825                 close(fd);
826         if (mp)
827                 camel_object_unref((CamelObject *)mp);
828         if (info)
829                 camel_message_info_free((CamelMessageInfo *)info);
830
831         camel_operation_end(NULL);
832
833         return -1;
834 }
835
836 static int
837 mbox_summary_sync(CamelLocalSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex)
838 {
839         struct stat st;
840         CamelMboxSummary *mbs = (CamelMboxSummary *)cls;
841         CamelFolderSummary *s = (CamelFolderSummary *)cls;
842         int i, count;
843         int quick = TRUE, work=FALSE;
844         int ret;
845
846         /* first, sync ourselves up, just to make sure */
847         if (camel_local_summary_check(cls, changeinfo, ex) == -1)
848                 return -1;
849
850         count = camel_folder_summary_count(s);
851         if (count == 0)
852                 return 0;
853
854         /* check what work we have to do, if any */
855         for (i=0;quick && i<count; i++) {
856                 CamelMboxMessageInfo *info = (CamelMboxMessageInfo *)camel_folder_summary_index(s, i);
857
858                 g_assert(info);
859                 if ((expunge && (info->info.info.flags & CAMEL_MESSAGE_DELETED)) ||
860                     (info->info.info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV|CAMEL_MESSAGE_FOLDER_XEVCHANGE)))
861                         quick = FALSE;
862                 else
863                         work |= (info->info.info.flags & CAMEL_MESSAGE_FOLDER_FLAGGED) != 0;
864                 camel_message_info_free(info);
865         }
866
867         /* yuck i hate this logic, but its to simplify the 'all ok, update summary' and failover cases */
868         ret = -1;
869         if (quick) {
870                 if (work) {
871                         ret = ((CamelMboxSummaryClass *)((CamelObject *)cls)->klass)->sync_quick(mbs, expunge, changeinfo, ex);
872                         if (ret == -1) {
873                                 g_warning("failed a quick-sync, trying a full sync");
874                                 camel_exception_clear(ex);
875                         }
876                 } else {
877                         ret = 0;
878                 }
879         }
880
881         if (ret == -1)
882                 ret = ((CamelMboxSummaryClass *)((CamelObject *)cls)->klass)->sync_full(mbs, expunge, changeinfo, ex);
883         if (ret == -1)
884                 return -1;
885
886         if (g_stat(cls->folder_path, &st) == -1) {
887                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
888                                       _("Unknown error: %s"), g_strerror (errno));
889                 return -1;
890         }
891
892         if (mbs->folder_size != st.st_size || s->time != st.st_mtime) {
893                 s->time = st.st_mtime;
894                 mbs->folder_size = st.st_size;
895                 camel_folder_summary_touch(s);
896         }
897
898         return ((CamelLocalSummaryClass *)camel_mbox_summary_parent)->sync(cls, expunge, changeinfo, ex);
899 }
900
901 int
902 camel_mbox_summary_sync_mbox(CamelMboxSummary *cls, guint32 flags, CamelFolderChangeInfo *changeinfo, int fd, int fdout, CamelException *ex)
903 {
904         CamelMboxSummary *mbs = (CamelMboxSummary *)cls;
905         CamelFolderSummary *s = (CamelFolderSummary *)mbs;
906         CamelMimeParser *mp = NULL;
907         int i, count;
908         CamelMboxMessageInfo *info = NULL;
909         char *buffer, *xevnew = NULL;
910         size_t len;
911         const char *fromline;
912         int lastdel = FALSE;
913 #ifdef STATUS_PINE
914         char statnew[8], xstatnew[8];
915 #endif
916
917         d(printf("performing full summary/sync\n"));
918
919         /* need to dup this because the mime-parser owns the fd after we give it to it */
920         fd = dup(fd);
921         if (fd == -1) {
922                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
923                                       _("Could not store folder: %s"),
924                                       g_strerror (errno));
925                 return -1;
926         }
927
928         mp = camel_mime_parser_new();
929         camel_mime_parser_scan_from(mp, TRUE);
930         camel_mime_parser_scan_pre_from(mp, TRUE);
931         camel_mime_parser_init_with_fd(mp, fd);
932
933         count = camel_folder_summary_count(s);
934         for (i = 0; i < count; i++) {
935                 int pc = (i + 1) * 100 / count;
936
937                 camel_operation_progress(NULL, pc);
938
939                 info = (CamelMboxMessageInfo *)camel_folder_summary_index(s, i);
940
941                 g_assert(info);
942
943                 d(printf("Looking at message %s\n", camel_message_info_uid(info)));
944
945                 /* only need to seek past deleted messages, otherwise we should be at the right spot/state already */
946                 if (lastdel) {
947                         d(printf("seeking to %d\n", (int)info->frompos));
948                         camel_mime_parser_seek(mp, info->frompos, SEEK_SET);
949                 }
950
951                 if (camel_mime_parser_step(mp, &buffer, &len) != CAMEL_MIME_PARSER_STATE_FROM) {
952                         g_warning("Expected a From line here, didn't get it");
953                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
954                                              _("Summary and folder mismatch, even after a sync"));
955                         goto error;
956                 }
957
958                 if (camel_mime_parser_tell_start_from(mp) != info->frompos) {
959                         g_warning("Didn't get the next message where I expected (%d) got %d instead",
960                                   (int)info->frompos, (int)camel_mime_parser_tell_start_from(mp));
961                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
962                                              _("Summary and folder mismatch, even after a sync"));
963                         goto error;
964                 }
965
966                 lastdel = FALSE;
967                 if ((flags&1) && info->info.info.flags & CAMEL_MESSAGE_DELETED) {
968                         const char *uid = camel_message_info_uid(info);
969
970                         d(printf("Deleting %s\n", uid));
971
972                         if (((CamelLocalSummary *)cls)->index)
973                                 camel_index_delete_name(((CamelLocalSummary *)cls)->index, uid);
974
975                         /* remove it from the change list */
976                         camel_folder_change_info_remove_uid(changeinfo, uid);
977                         camel_folder_summary_remove(s, (CamelMessageInfo *)info);
978                         camel_message_info_free((CamelMessageInfo *)info);
979                         count--;
980                         i--;
981                         info = NULL;
982                         lastdel = TRUE;
983                 } else {
984                         /* otherwise, the message is staying, copy its From_ line across */
985 #if 0
986                         if (i>0)
987                                 write(fdout, "\n", 1);
988 #endif
989                         info->frompos = lseek(fdout, 0, SEEK_CUR);
990                         fromline = camel_mime_parser_from_line(mp);
991                         write(fdout, fromline, strlen(fromline));
992                 }
993
994                 if (info && info->info.info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV | CAMEL_MESSAGE_FOLDER_FLAGGED)) {
995                         d(printf("Updating header for %s flags = %08x\n", camel_message_info_uid(info), info->info.flags));
996
997                         if (camel_mime_parser_step(mp, &buffer, &len) == CAMEL_MIME_PARSER_STATE_FROM_END) {
998                                 g_warning("camel_mime_parser_step failed (2)");
999                                 goto error;
1000                         }
1001
1002                         xevnew = camel_local_summary_encode_x_evolution((CamelLocalSummary *)cls, &info->info);
1003 #ifdef STATUS_PINE
1004                         if (mbs->xstatus) {
1005                                 encode_status(info->info.info.flags & STATUS_STATUS, statnew);
1006                                 encode_status(info->info.info.flags & STATUS_XSTATUS, xstatnew);
1007                                 len = camel_local_summary_write_headers(fdout, camel_mime_parser_headers_raw(mp), xevnew, statnew, xstatnew);
1008                         } else {
1009 #endif
1010                                 len = camel_local_summary_write_headers(fdout, camel_mime_parser_headers_raw(mp), xevnew, NULL, NULL);
1011 #ifdef STATUS_PINE
1012                         }
1013 #endif
1014                         if (len == -1) {
1015                                 d(printf("Writing to temporary mailbox failed\n"));
1016                                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1017                                                       _("Writing to temporary mailbox failed: %s"),
1018                                                       g_strerror (errno));
1019                                 goto error;
1020                         }
1021                         info->info.info.flags &= 0xffff;
1022                         g_free(xevnew);
1023                         xevnew = NULL;
1024                         camel_mime_parser_drop_step(mp);
1025                 }
1026
1027                 camel_mime_parser_drop_step(mp);
1028                 if (info) {
1029                         d(printf("looking for message content to copy across from %d\n", (int)camel_mime_parser_tell(mp)));
1030                         while (camel_mime_parser_step(mp, &buffer, &len) == CAMEL_MIME_PARSER_STATE_PRE_FROM) {
1031                                 /*d(printf("copying mbox contents to temporary: '%.*s'\n", len, buffer));*/
1032                                 if (write(fdout, buffer, len) != len) {
1033                                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1034                                                               _("Writing to temporary mailbox failed: %s: %s"),
1035                                                               ((CamelLocalSummary *)cls)->folder_path,
1036                                                               g_strerror (errno));
1037                                         goto error;
1038                                 }
1039                         }
1040
1041                         if (write(fdout, "\n", 1) != 1) {
1042                                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
1043                                                       _("Writing to temporary mailbox failed: %s"),
1044                                                       g_strerror (errno));
1045                                 goto error;
1046                         }
1047
1048                         d(printf("we are now at %d, from = %d\n", (int)camel_mime_parser_tell(mp),
1049                                  (int)camel_mime_parser_tell_start_from(mp)));
1050                         camel_mime_parser_unstep(mp);
1051                         camel_message_info_free((CamelMessageInfo *)info);
1052                         info = NULL;
1053                 }
1054         }
1055
1056 #if 0
1057         /* if last was deleted, append the \n we removed */
1058         if (lastdel && count > 0)
1059                 write(fdout, "\n", 1);
1060 #endif
1061
1062         camel_object_unref((CamelObject *)mp);
1063
1064         /* clear working flags */
1065         for (i=0; i<count; i++) {
1066                 info = (CamelMboxMessageInfo *)camel_folder_summary_index(s, i);
1067                 if (info) {
1068                         if (info->info.info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV|CAMEL_MESSAGE_FOLDER_FLAGGED|CAMEL_MESSAGE_FOLDER_XEVCHANGE)) {
1069                                 info->info.info.flags &= ~(CAMEL_MESSAGE_FOLDER_NOXEV
1070                                                            |CAMEL_MESSAGE_FOLDER_FLAGGED
1071                                                            |CAMEL_MESSAGE_FOLDER_XEVCHANGE);
1072                                 camel_folder_summary_touch(s);
1073                         }
1074                         camel_message_info_free((CamelMessageInfo *)info);
1075                 }
1076         }
1077                 
1078         return 0;
1079  error:
1080         g_free(xevnew);
1081         
1082         if (mp)
1083                 camel_object_unref((CamelObject *)mp);
1084         if (info)
1085                 camel_message_info_free((CamelMessageInfo *)info);
1086
1087         return -1;
1088 }
1089
1090 #ifdef STATUS_PINE
1091 static CamelMessageInfo *
1092 mbox_summary_add(CamelLocalSummary *cls, CamelMimeMessage *msg, const CamelMessageInfo *info, CamelFolderChangeInfo *ci, CamelException *ex)
1093 {
1094         CamelMboxMessageInfo *mi;
1095
1096         mi = (CamelMboxMessageInfo *)((CamelLocalSummaryClass *)camel_mbox_summary_parent)->add(cls, msg, info, ci, ex);
1097         if (mi && ((CamelMboxSummary *)cls)->xstatus) {
1098                 char status[8];
1099
1100                 /* we snoop and add status/x-status headers to suit */
1101                 encode_status(mi->info.info.flags & STATUS_STATUS, status);
1102                 camel_medium_set_header((CamelMedium *)msg, "Status", status);
1103                 encode_status(mi->info.info.flags & STATUS_XSTATUS, status);
1104                 camel_medium_set_header((CamelMedium *)msg, "X-Status", status);
1105         }
1106
1107         return (CamelMessageInfo *)mi;
1108 }
1109
1110 static struct {
1111         char tag;
1112         guint32 flag;
1113 } status_flags[] = {
1114         { 'F', CAMEL_MESSAGE_FLAGGED },
1115         { 'A', CAMEL_MESSAGE_ANSWERED },
1116         { 'D', CAMEL_MESSAGE_DELETED },
1117         { 'R', CAMEL_MESSAGE_SEEN },
1118 };
1119
1120 static void
1121 encode_status(guint32 flags, char status[8])
1122 {
1123         size_t i;
1124         char *p;
1125         
1126         p = status;
1127         for (i = 0; i < G_N_ELEMENTS (status_flags); i++)
1128                 if (status_flags[i].flag & flags)
1129                         *p++ = status_flags[i].tag;
1130         *p++ = 'O';
1131         *p = '\0';
1132 }
1133
1134 static guint32
1135 decode_status(const char *status)
1136 {
1137         const char *p;
1138         guint32 flags = 0;
1139         size_t i;
1140         char c;
1141         
1142         p = status;
1143         while ((c = *p++)) {
1144                 for (i = 0; i < G_N_ELEMENTS (status_flags); i++)
1145                         if (status_flags[i].tag == c)
1146                                 flags |= status_flags[i].flag;
1147         }
1148         
1149         return flags;
1150 }
1151
1152 #endif /* STATUS_PINE */