f5332dc140cc6b0299cc29172c4475857732219c
[platform/upstream/evolution-data-server.git] / camel / providers / local / camel-mbox-folder.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8; fill-column: 160 -*-
2  *
3  * Authors: Michael Zucchi <notzed@ximian.com>
4  *
5  * Copyright (C) 1999, 2000 Ximian Inc.
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 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
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
19  * USA
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdlib.h>
27 #include <sys/types.h>
28 #include <dirent.h>
29 #include <sys/stat.h>
30 #include <unistd.h>
31 #include <errno.h>
32 #include <string.h>
33 #include <fcntl.h>
34
35 #include "camel-mbox-folder.h"
36 #include "camel-mbox-store.h"
37 #include "string-utils.h"
38 #include "camel-stream-fs.h"
39 #include "camel-mbox-summary.h"
40 #include "camel-data-wrapper.h"
41 #include "camel-mime-message.h"
42 #include "camel-stream-filter.h"
43 #include "camel-mime-filter-from.h"
44 #include "camel-exception.h"
45
46 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
47
48 static CamelLocalFolderClass *parent_class = NULL;
49
50 /* Returns the class for a CamelMboxFolder */
51 #define CMBOXF_CLASS(so) CAMEL_MBOX_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
52 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
53 #define CMBOXS_CLASS(so) CAMEL_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
54
55 static int mbox_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex);
56 static void mbox_unlock(CamelLocalFolder *lf);
57
58 #ifdef STATUS_PINE
59 static void mbox_set_message_flags(CamelFolder *folder, const char *uid, guint32 flags, guint32 set);
60 #endif
61
62 static void mbox_set_message_user_flag(CamelFolder *folder, const char *uid, const char *name, gboolean value);
63 static void mbox_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value);
64
65 static void mbox_append_message(CamelFolder *folder, CamelMimeMessage * message, const CamelMessageInfo * info, char **appended_uid, CamelException *ex);
66 static CamelMimeMessage *mbox_get_message(CamelFolder *folder, const gchar * uid, CamelException *ex);
67 static CamelLocalSummary *mbox_create_summary(const char *path, const char *folder, CamelIndex *index);
68
69 static void mbox_finalise(CamelObject * object);
70
71 static void
72 camel_mbox_folder_class_init(CamelMboxFolderClass * camel_mbox_folder_class)
73 {
74         CamelFolderClass *camel_folder_class = CAMEL_FOLDER_CLASS(camel_mbox_folder_class);
75         CamelLocalFolderClass *lclass = (CamelLocalFolderClass *)camel_mbox_folder_class;
76
77         parent_class = (CamelLocalFolderClass *)camel_type_get_global_classfuncs(camel_local_folder_get_type());
78
79         /* virtual method definition */
80
81         /* virtual method overload */
82         camel_folder_class->append_message = mbox_append_message;
83         camel_folder_class->get_message = mbox_get_message;
84
85 #ifdef STATUS_PINE
86         camel_folder_class->set_message_flags = mbox_set_message_flags;
87 #endif
88         camel_folder_class->set_message_user_flag = mbox_set_message_user_flag;
89         camel_folder_class->set_message_user_tag = mbox_set_message_user_tag;
90
91         lclass->create_summary = mbox_create_summary;
92         lclass->lock = mbox_lock;
93         lclass->unlock = mbox_unlock;
94 }
95
96 static void
97 mbox_init(gpointer object, gpointer klass)
98 {
99         /*CamelFolder *folder = object;*/
100         CamelMboxFolder *mbox_folder = object;
101
102         mbox_folder->lockfd = -1;
103 }
104
105 static void
106 mbox_finalise(CamelObject * object)
107 {
108         CamelMboxFolder *mbox_folder = (CamelMboxFolder *)object;
109
110         g_assert(mbox_folder->lockfd == -1);
111 }
112
113 CamelType camel_mbox_folder_get_type(void)
114 {
115         static CamelType camel_mbox_folder_type = CAMEL_INVALID_TYPE;
116
117         if (camel_mbox_folder_type == CAMEL_INVALID_TYPE) {
118                 camel_mbox_folder_type = camel_type_register(CAMEL_LOCAL_FOLDER_TYPE, "CamelMboxFolder",
119                                                              sizeof(CamelMboxFolder),
120                                                              sizeof(CamelMboxFolderClass),
121                                                              (CamelObjectClassInitFunc) camel_mbox_folder_class_init,
122                                                              NULL,
123                                                              (CamelObjectInitFunc) mbox_init,
124                                                              (CamelObjectFinalizeFunc) mbox_finalise);
125         }
126
127         return camel_mbox_folder_type;
128 }
129
130 CamelFolder *
131 camel_mbox_folder_new(CamelStore *parent_store, const char *full_name, guint32 flags, CamelException *ex)
132 {
133         CamelFolder *folder;
134
135         d(printf("Creating mbox folder: %s in %s\n", full_name, camel_local_store_get_toplevel_dir((CamelLocalStore *)parent_store)));
136
137         folder = (CamelFolder *)camel_object_new(CAMEL_MBOX_FOLDER_TYPE);
138         folder = (CamelFolder *)camel_local_folder_construct((CamelLocalFolder *)folder,
139                                                              parent_store, full_name, flags, ex);
140
141         return folder;
142 }
143
144 static CamelLocalSummary *mbox_create_summary(const char *path, const char *folder, CamelIndex *index)
145 {
146         return (CamelLocalSummary *)camel_mbox_summary_new(path, folder, index);
147 }
148
149 static int mbox_lock(CamelLocalFolder *lf, CamelLockType type, CamelException *ex)
150 {
151         CamelMboxFolder *mf = (CamelMboxFolder *)lf;
152
153         /* make sure we have matching unlocks for locks, camel-local-folder class should enforce this */
154         g_assert(mf->lockfd == -1);
155
156         mf->lockfd = open(lf->folder_path, O_RDWR, 0);
157         if (mf->lockfd == -1) {
158                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
159                                       _("Cannot create folder lock on %s: %s"),
160                                       lf->folder_path, g_strerror (errno));
161                 return -1;
162         }
163
164         if (camel_lock_folder(lf->folder_path, mf->lockfd, type, ex) == -1) {
165                 close(mf->lockfd);
166                 mf->lockfd = -1;
167                 return -1;
168         }
169
170         return 0;
171 }
172
173 static void mbox_unlock(CamelLocalFolder *lf)
174 {
175         CamelMboxFolder *mf = (CamelMboxFolder *)lf;
176
177         g_assert(mf->lockfd != -1);
178         camel_unlock_folder(lf->folder_path, mf->lockfd);
179         close(mf->lockfd);
180         mf->lockfd = -1;
181 }
182
183 static void
184 mbox_append_message(CamelFolder *folder, CamelMimeMessage * message, const CamelMessageInfo * info, char **appended_uid, CamelException *ex)
185 {
186         CamelLocalFolder *lf = (CamelLocalFolder *)folder;
187         CamelStream *output_stream = NULL, *filter_stream = NULL;
188         CamelMimeFilter *filter_from = NULL;
189         CamelMboxSummary *mbs = (CamelMboxSummary *)folder->summary;
190         CamelMessageInfo *mi;
191         char *fromline = NULL;
192         int fd, retval;
193         struct stat st;
194 #if 0
195         char *xev;
196 #endif
197         /* If we can't lock, dont do anything */
198         if (camel_local_folder_lock(lf, CAMEL_LOCK_WRITE, ex) == -1)
199                 return;
200
201         d(printf("Appending message\n"));
202
203         /* first, check the summary is correct (updates folder_size too) */
204         retval = camel_local_summary_check ((CamelLocalSummary *)folder->summary, lf->changes, ex);
205         if (retval == -1)
206                 goto fail;
207
208         /* add it to the summary/assign the uid, etc */
209         mi = camel_local_summary_add((CamelLocalSummary *)folder->summary, message, info, lf->changes, ex);
210         if (mi == NULL)
211                 goto fail;
212
213         d(printf("Appending message: uid is %s\n", camel_message_info_uid(mi)));
214
215         output_stream = camel_stream_fs_new_with_name(lf->folder_path, O_WRONLY|O_APPEND, 0600);
216         if (output_stream == NULL) {
217                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
218                                       _("Cannot open mailbox: %s: %s\n"),
219                                       lf->folder_path, g_strerror (errno));
220                 goto fail;
221         }
222
223         /* and we need to set the frompos/XEV explicitly */
224         ((CamelMboxMessageInfo *)mi)->frompos = mbs->folder_size;
225 #if 0
226         xev = camel_local_summary_encode_x_evolution((CamelLocalSummary *)folder->summary, mi);
227         if (xev) {
228                 /* the x-ev header should match the 'current' flags, no problem, so store as much */
229                 camel_medium_set_header((CamelMedium *)message, "X-Evolution", xev);
230                 mi->flags &= ~ CAMEL_MESSAGE_FOLDER_NOXEV|CAMEL_MESSAGE_FOLDER_FLAGGED;
231                 g_free(xev);
232         }
233 #endif
234
235         /* we must write this to the non-filtered stream ... */
236         fromline = camel_mime_message_build_mbox_from(message);
237         if (camel_stream_write(output_stream, fromline, strlen(fromline)) == -1)
238                 goto fail_write;
239
240         /* and write the content to the filtering stream, that translates '\nFrom' into '\n>From' */
241         filter_stream = (CamelStream *) camel_stream_filter_new_with_stream(output_stream);
242         filter_from = (CamelMimeFilter *) camel_mime_filter_from_new();
243         camel_stream_filter_add((CamelStreamFilter *) filter_stream, filter_from);
244         if (camel_data_wrapper_write_to_stream((CamelDataWrapper *)message, filter_stream) == -1
245             || camel_stream_write(filter_stream, "\n", 1) == -1
246             || camel_stream_close(filter_stream) == -1)
247                 goto fail_write;
248
249         /* filter stream ref's the output stream itself, so we need to unref it too */
250         camel_object_unref((CamelObject *)filter_from);
251         camel_object_unref((CamelObject *)filter_stream);
252         camel_object_unref((CamelObject *)output_stream);
253         g_free(fromline);
254
255         /* now we 'fudge' the summary  to tell it its uptodate, because its idea of uptodate has just changed */
256         /* the stat really shouldn't fail, we just wrote to it */
257         if (stat(lf->folder_path, &st) == 0) {
258                 mbs->folder_size = st.st_size;
259                 ((CamelFolderSummary *)mbs)->time = st.st_mtime;
260         }
261
262         /* unlock as soon as we can */
263         camel_local_folder_unlock(lf);
264
265         if (camel_folder_change_info_changed(lf->changes)) {
266                 camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
267                 camel_folder_change_info_clear(lf->changes);
268         }
269
270         if (appended_uid)
271                 *appended_uid = g_strdup(camel_message_info_uid(mi));
272
273         return;
274
275 fail_write:
276         if (errno == EINTR)
277                 camel_exception_set (ex, CAMEL_EXCEPTION_USER_CANCEL,
278                                      _("Mail append cancelled"));
279         else
280                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
281                                       _("Cannot append message to mbox file: %s: %s"),
282                                       lf->folder_path, g_strerror (errno));
283         
284         if (filter_stream)
285                 camel_object_unref(CAMEL_OBJECT(filter_stream));
286
287         if (output_stream)
288                 camel_object_unref(CAMEL_OBJECT(output_stream));
289
290         if (filter_from)
291                 camel_object_unref(CAMEL_OBJECT(filter_from));
292
293         g_free(fromline);
294
295         /* reset the file to original size */
296         fd = open(lf->folder_path, O_WRONLY, 0600);
297         if (fd != -1) {
298                 ftruncate(fd, mbs->folder_size);
299                 close(fd);
300         }
301         
302         /* remove the summary info so we are not out-of-sync with the mbox */
303         camel_folder_summary_remove_uid (CAMEL_FOLDER_SUMMARY (mbs), camel_message_info_uid (mi));
304         
305         /* and tell the summary its uptodate */
306         if (stat(lf->folder_path, &st) == 0) {
307                 mbs->folder_size = st.st_size;
308                 ((CamelFolderSummary *)mbs)->time = st.st_mtime;
309         }
310         
311 fail:
312         /* make sure we unlock the folder - before we start triggering events into appland */
313         camel_local_folder_unlock(lf);
314
315         /* cascade the changes through, anyway, if there are any outstanding */
316         if (camel_folder_change_info_changed(lf->changes)) {
317                 camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
318                 camel_folder_change_info_clear(lf->changes);
319         }
320 }
321
322 static CamelMimeMessage *
323 mbox_get_message(CamelFolder *folder, const gchar * uid, CamelException *ex)
324 {
325         CamelLocalFolder *lf = (CamelLocalFolder *)folder;
326         CamelMimeMessage *message = NULL;
327         CamelMboxMessageInfo *info;
328         CamelMimeParser *parser = NULL;
329         int fd, retval;
330         int retried = FALSE;
331         off_t frompos;
332
333         d(printf("Getting message %s\n", uid));
334
335         /* lock the folder first, burn if we can't, need write lock for summary check */
336         if (camel_local_folder_lock(lf, CAMEL_LOCK_WRITE, ex) == -1)
337                 return NULL;
338
339         /* check for new messages always */
340         if (camel_local_summary_check((CamelLocalSummary *)folder->summary, lf->changes, ex) == -1) {
341                 camel_local_folder_unlock(lf);
342                 return NULL;
343         }
344         
345 retry:
346         /* get the message summary info */
347         info = (CamelMboxMessageInfo *) camel_folder_summary_uid(folder->summary, uid);
348
349         if (info == NULL) {
350                 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
351                                      _("Cannot get message: %s\n  %s"), uid, _("No such message"));
352                 goto fail;
353         }
354
355         /* no frompos, its an error in the library (and we can't do anything with it) */
356         g_assert(info->frompos != -1);
357
358         frompos = info->frompos;
359         camel_folder_summary_info_free(folder->summary, (CamelMessageInfo *)info);
360         
361         /* we use an fd instead of a normal stream here - the reason is subtle, camel_mime_part will cache
362            the whole message in memory if the stream is non-seekable (which it is when built from a parser
363            with no stream).  This means we dont have to lock the mbox for the life of the message, but only
364            while it is being created. */
365
366         fd = open(lf->folder_path, O_RDONLY);
367         if (fd == -1) {
368                 camel_exception_setv (ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
369                                       _("Cannot get message: %s from folder %s\n  %s"),
370                                       uid, lf->folder_path, g_strerror (errno));
371                 goto fail;
372         }
373
374         /* we use a parser to verify the message is correct, and in the correct position */
375         parser = camel_mime_parser_new();
376         camel_mime_parser_init_with_fd(parser, fd);
377         camel_mime_parser_scan_from(parser, TRUE);
378
379         camel_mime_parser_seek(parser, frompos, SEEK_SET);
380         if (camel_mime_parser_step(parser, NULL, NULL) != HSCAN_FROM
381             || camel_mime_parser_tell_start_from(parser) != frompos) {
382
383                 g_warning("Summary doesn't match the folder contents!  eek!\n"
384                           "  expecting offset %ld got %ld, state = %d", (long int)frompos,
385                           (long int)camel_mime_parser_tell_start_from(parser),
386                           camel_mime_parser_state(parser));
387
388                 camel_object_unref((CamelObject *)parser);
389                 parser = NULL;
390
391                 if (!retried) {
392                         retried = TRUE;
393                         camel_local_summary_check_force((CamelLocalSummary *)folder->summary);
394                         retval = camel_local_summary_check((CamelLocalSummary *)folder->summary, lf->changes, ex);
395                         if (retval != -1)
396                                 goto retry;
397                 }
398
399                 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_INVALID_UID,
400                                      _("Cannot get message: %s from folder %s\n  %s"), uid, lf->folder_path,
401                                      _("The folder appears to be irrecoverably corrupted."));
402                 goto fail;
403         }
404         
405         message = camel_mime_message_new();
406         if (camel_mime_part_construct_from_parser((CamelMimePart *)message, parser) == -1) {
407                 camel_exception_setv(ex, errno==EINTR?CAMEL_EXCEPTION_USER_CANCEL:CAMEL_EXCEPTION_FOLDER_INVALID_UID,
408                                      _("Cannot get message: %s from folder %s\n  %s"), uid, lf->folder_path,
409                                      _("Message construction failed: Corrupt mailbox?"));
410                 camel_object_unref((CamelObject *)message);
411                 message = NULL;
412                 goto fail;
413         }
414
415         camel_medium_remove_header((CamelMedium *)message, "X-Evolution");
416 fail:
417         /* and unlock now we're finished with it */
418         camel_local_folder_unlock(lf);
419
420         if (parser)
421                 camel_object_unref((CamelObject *)parser);
422         
423         /* use the opportunity to notify of changes (particularly if we had a rebuild) */
424         if (camel_folder_change_info_changed(lf->changes)) {
425                 camel_object_trigger_event((CamelObject *)folder, "folder_changed", lf->changes);
426                 camel_folder_change_info_clear(lf->changes);
427         }
428         
429         return message;
430 }
431
432 #ifdef STATUS_PINE
433 static void
434 mbox_set_message_flags(CamelFolder *folder, const char *uid, guint32 flags, guint32 set)
435 {
436         /* Basically, if anything could change the Status line, presume it does */
437         if (((CamelMboxSummary *)folder->summary)->xstatus
438             && (flags & (CAMEL_MESSAGE_SEEN|CAMEL_MESSAGE_FLAGGED|CAMEL_MESSAGE_ANSWERED|CAMEL_MESSAGE_DELETED))) {
439                 flags |= CAMEL_MESSAGE_FOLDER_XEVCHANGE|CAMEL_MESSAGE_FOLDER_FLAGGED;
440                 set |= CAMEL_MESSAGE_FOLDER_XEVCHANGE|CAMEL_MESSAGE_FOLDER_FLAGGED;
441         }
442
443         ((CamelFolderClass *)parent_class)->set_message_flags(folder, uid, flags, set);
444 }
445 #endif
446
447 static void
448 mbox_set_message_user_flag(CamelFolder *folder, const char *uid, const char *name, gboolean value)
449 {
450         CamelMessageInfo *info;
451
452         g_return_if_fail(folder->summary != NULL);
453
454         info = camel_folder_summary_uid(folder->summary, uid);
455         if (info == NULL)
456                 return;
457
458         if (camel_flag_set(&info->user_flags, name, value)) {
459                 info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED|CAMEL_MESSAGE_FOLDER_XEVCHANGE;
460                 camel_folder_summary_touch(folder->summary);
461                 camel_object_trigger_event(CAMEL_OBJECT(folder), "message_changed", (char *) uid);
462         }
463         camel_folder_summary_info_free(folder->summary, info);
464 }
465
466 static void
467 mbox_set_message_user_tag(CamelFolder *folder, const char *uid, const char *name, const char *value)
468 {
469         CamelMessageInfo *info;
470
471         g_return_if_fail(folder->summary != NULL);
472
473         info = camel_folder_summary_uid(folder->summary, uid);
474         if (info == NULL)
475                 return;
476
477         if (camel_tag_set(&info->user_tags, name, value)) {
478                 info->flags |= CAMEL_MESSAGE_FOLDER_FLAGGED|CAMEL_MESSAGE_FOLDER_XEVCHANGE;
479                 camel_folder_summary_touch(folder->summary);
480                 camel_object_trigger_event(CAMEL_OBJECT(folder), "message_changed", (char *) uid);
481         }
482         camel_folder_summary_info_free(folder->summary, info);
483 }