Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / local / camel-mh-summary.c
1 /*
2  *  Copyright (C) 2000 Ximian Inc.
3  *
4  *  Authors: Not Zed <notzed@lostzed.mmc.com.au>
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of version 2 of the GNU Lesser General Public
8  * License as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU Lesser General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  * Boston, MA 02110-1301, USA.
19  */
20
21 #ifdef HAVE_CONFIG_H
22 #include <config.h>
23 #endif
24
25 #include <ctype.h>
26 #include <dirent.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/gi18n-lib.h>
36
37 #include "camel-mime-message.h"
38 #include "camel-private.h"
39
40 #include "camel-mh-summary.h"
41
42 #define d(x) /*(printf("%s(%d): ", __FILE__, __LINE__),(x))*/
43
44 #define CAMEL_MH_SUMMARY_VERSION (0x2000)
45
46 static int mh_summary_check(CamelLocalSummary *cls, CamelFolderChangeInfo *changeinfo, CamelException *ex);
47 static int mh_summary_sync(CamelLocalSummary *cls, gboolean expunge, CamelFolderChangeInfo *changeinfo, CamelException *ex);
48 /*static int mh_summary_add(CamelLocalSummary *cls, CamelMimeMessage *msg, CamelMessageInfo *info, CamelFolderChangeInfo *, CamelException *ex);*/
49
50 static char *mh_summary_next_uid_string(CamelFolderSummary *s);
51
52 static void camel_mh_summary_class_init (CamelMhSummaryClass *class);
53 static void camel_mh_summary_init       (CamelMhSummary *gspaper);
54 static void camel_mh_summary_finalise   (CamelObject *obj);
55
56 #define _PRIVATE(x) (((CamelMhSummary *)(x))->priv)
57
58 struct _CamelMhSummaryPrivate {
59         char *current_uid;
60 };
61
62 static CamelLocalSummaryClass *parent_class;
63
64 CamelType
65 camel_mh_summary_get_type (void)
66 {
67         static CamelType type = CAMEL_INVALID_TYPE;
68         
69         if (type == CAMEL_INVALID_TYPE) {
70                 type = camel_type_register(camel_local_summary_get_type (), "CamelMhSummary",
71                                            sizeof(CamelMhSummary),
72                                            sizeof(CamelMhSummaryClass),
73                                            (CamelObjectClassInitFunc)camel_mh_summary_class_init,
74                                            NULL,
75                                            (CamelObjectInitFunc)camel_mh_summary_init,
76                                            (CamelObjectFinalizeFunc)camel_mh_summary_finalise);
77         }
78         
79         return type;
80 }
81
82 static void
83 camel_mh_summary_class_init (CamelMhSummaryClass *class)
84 {
85         CamelFolderSummaryClass *sklass = (CamelFolderSummaryClass *) class;
86         CamelLocalSummaryClass *lklass = (CamelLocalSummaryClass *)class;
87
88         parent_class = (CamelLocalSummaryClass *)camel_type_get_global_classfuncs(camel_local_summary_get_type ());
89
90         /* override methods */
91         sklass->next_uid_string = mh_summary_next_uid_string;
92
93         lklass->check = mh_summary_check;
94         lklass->sync = mh_summary_sync;
95         /*lklass->add = mh_summary_add;*/
96 }
97
98 static void
99 camel_mh_summary_init (CamelMhSummary *o)
100 {
101         struct _CamelFolderSummary *s = (CamelFolderSummary *) o;
102
103         o->priv = g_malloc0(sizeof(*o->priv));
104         /* set unique file version */
105         s->version += CAMEL_MH_SUMMARY_VERSION;
106 }
107
108 static void
109 camel_mh_summary_finalise(CamelObject *obj)
110 {
111         CamelMhSummary *o = (CamelMhSummary *)obj;
112
113         g_free(o->priv);
114 }
115
116 /**
117  * camel_mh_summary_new:
118  *
119  * Create a new CamelMhSummary object.
120  * 
121  * Return value: A new #CamelMhSummary object.
122  **/
123 CamelMhSummary  *camel_mh_summary_new(struct _CamelFolder *folder, const char *filename, const char *mhdir, CamelIndex *index)
124 {
125         CamelMhSummary *o = (CamelMhSummary *)camel_object_new(camel_mh_summary_get_type ());
126
127         ((CamelFolderSummary *)o)->folder = folder;
128
129         camel_local_summary_construct((CamelLocalSummary *)o, filename, mhdir, index);
130         return o;
131 }
132
133 static char *mh_summary_next_uid_string(CamelFolderSummary *s)
134 {
135         CamelMhSummary *mhs = (CamelMhSummary *)s;
136         CamelLocalSummary *cls = (CamelLocalSummary *)s;
137         int fd = -1;
138         guint32 uid;
139         char *name;
140         char *uidstr;
141
142         /* if we are working to add an existing file, then use current_uid */
143         if (mhs->priv->current_uid) {
144                 uidstr = g_strdup(mhs->priv->current_uid);
145                 /* tell the summary of this, so we always append numbers to the end */
146                 camel_folder_summary_set_uid(s, strtoul(uidstr, NULL, 10)+1);
147         } else {
148                 /* else scan for one - and create it too, to make sure */
149                 do {
150                         if (fd != -1)
151                                 close(fd);
152                         uid = camel_folder_summary_next_uid(s);
153                         name = g_strdup_printf("%s/%u", cls->folder_path, uid);
154                         /* O_EXCL isn't guaranteed, sigh.  Oh well, bad luck, mh has problems anyway */
155                         fd = open(name, O_WRONLY|O_CREAT|O_EXCL, 0600);
156                         g_free(name);
157                 } while (fd == -1 && errno == EEXIST);
158
159                 if (fd != -1)
160                         close(fd);
161
162                 uidstr = g_strdup_printf("%u", uid);
163         }
164
165         return uidstr;
166 }
167
168 static int camel_mh_summary_add(CamelLocalSummary *cls, const char *name, int forceindex)
169 {
170         CamelMhSummary *mhs = (CamelMhSummary *)cls;
171         char *filename = g_strdup_printf("%s/%s", cls->folder_path, name);
172         int fd;
173         CamelMimeParser *mp;
174
175         d(printf("summarising: %s\n", name));
176
177         fd = open(filename, O_RDONLY);
178         if (fd == -1) {
179                 g_warning ("Cannot summarise/index: %s: %s", filename, strerror (errno));
180                 g_free(filename);
181                 return -1;
182         }
183         mp = camel_mime_parser_new();
184         camel_mime_parser_scan_from(mp, FALSE);
185         camel_mime_parser_init_with_fd(mp, fd);
186         if (cls->index && (forceindex || !camel_index_has_name(cls->index, name))) {
187                 d(printf("forcing indexing of message content\n"));
188                 camel_folder_summary_set_index((CamelFolderSummary *)mhs, cls->index);
189         } else {
190                 camel_folder_summary_set_index((CamelFolderSummary *)mhs, NULL);
191         }
192         mhs->priv->current_uid = (char *)name;
193         camel_folder_summary_add_from_parser((CamelFolderSummary *)mhs, mp);
194         camel_object_unref((CamelObject *)mp);
195         mhs->priv->current_uid = NULL;
196         camel_folder_summary_set_index((CamelFolderSummary *)mhs, NULL);
197         g_free(filename);
198         return 0;
199 }
200
201 static void
202 remove_summary(char *key, CamelMessageInfo *info, CamelLocalSummary *cls)
203 {
204         d(printf("removing message %s from summary\n", key));
205         if (cls->index)
206                 camel_index_delete_name(cls->index, camel_message_info_uid(info));
207         camel_folder_summary_remove((CamelFolderSummary *)cls, info);
208         camel_message_info_free(info);
209 }
210
211 static int
212 sort_uid_cmp(const void *ap, const void *bp)
213 {
214         const CamelMessageInfo
215                 *a = *((CamelMessageInfo **)ap),
216                 *b = *((CamelMessageInfo **)bp);
217         const char
218                 *auid = camel_message_info_uid(a),
219                 *buid = camel_message_info_uid(b);
220         int aval = atoi(auid), bval = atoi(buid);
221
222         return (aval < bval) ? -1 : (aval > bval) ? 1 : 0;
223 }
224
225 static int
226 mh_summary_check(CamelLocalSummary *cls, CamelFolderChangeInfo *changeinfo, CamelException *ex)
227 {
228         DIR *dir;
229         struct dirent *d;
230         char *p, c;
231         CamelMessageInfo *info;
232         CamelFolderSummary *s = (CamelFolderSummary *)cls;
233         GHashTable *left;
234         int i, count;
235         int forceindex;
236
237         /* FIXME: Handle changeinfo */
238
239         d(printf("checking summary ...\n"));
240
241         /* scan the directory, check for mail files not in the index, or index entries that
242            no longer exist */
243         dir = opendir(cls->folder_path);
244         if (dir == NULL) {
245                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
246                                       _("Cannot open MH directory path: %s: %s"),
247                                       cls->folder_path, g_strerror (errno));
248                 return -1;
249         }
250
251         /* keeps track of all uid's that have not been processed */
252         left = g_hash_table_new(g_str_hash, g_str_equal);
253         count = camel_folder_summary_count((CamelFolderSummary *)cls);
254         forceindex = count == 0;
255         for (i=0;i<count;i++) {
256                 info = camel_folder_summary_index((CamelFolderSummary *)cls, i);
257                 if (info) {
258                         g_hash_table_insert(left, (char *)camel_message_info_uid(info), info);
259                 }
260         }
261
262         while ( (d = readdir(dir)) ) {
263                 /* FIXME: also run stat to check for regular file */
264                 p = d->d_name;
265                 while ( (c = *p++) ) {
266                         if (!isdigit(c))
267                                 break;
268                 }
269                 if (c==0) {
270                         info = camel_folder_summary_uid((CamelFolderSummary *)cls, d->d_name);
271                         if (info == NULL || (cls->index && (!camel_index_has_name(cls->index, d->d_name)))) {
272                                 /* need to add this file to the summary */
273                                 if (info != NULL) {
274                                         g_hash_table_remove(left, camel_message_info_uid(info));
275                                         camel_folder_summary_remove((CamelFolderSummary *)cls, info);
276                                         camel_message_info_free(info);
277                                 }
278                                 camel_mh_summary_add(cls, d->d_name, forceindex);
279                         } else {
280                                 const char *uid = camel_message_info_uid(info);
281                                 CamelMessageInfo *old = g_hash_table_lookup(left, uid);
282
283                                 if (old) {
284                                         camel_message_info_free(old);
285                                         g_hash_table_remove(left, uid);
286                                 }
287                                 camel_message_info_free(info);
288                         }
289                 }
290         }
291         closedir(dir);
292         g_hash_table_foreach(left, (GHFunc)remove_summary, cls);
293         g_hash_table_destroy(left);
294
295         /* sort the summary based on message number (uid), since the directory order is not useful */
296         CAMEL_SUMMARY_LOCK(s, summary_lock);
297         qsort(s->messages->pdata, s->messages->len, sizeof(CamelMessageInfo *), sort_uid_cmp);
298         CAMEL_SUMMARY_UNLOCK(s, summary_lock);
299
300         return 0;
301 }
302
303 /* sync the summary file with the ondisk files */
304 static int
305 mh_summary_sync(CamelLocalSummary *cls, gboolean expunge, CamelFolderChangeInfo *changes, CamelException *ex)
306 {
307         int count, i;
308         CamelLocalMessageInfo *info;
309         char *name;
310         const char *uid;
311
312         d(printf("summary_sync(expunge=%s)\n", expunge?"true":"false"));
313
314         /* we could probably get away without this ... but why not use it, esp if we're going to
315            be doing any significant io already */
316         if (camel_local_summary_check(cls, changes, ex) == -1)
317                 return -1;
318
319         /* FIXME: need to update/honour .mh_sequences or whatever it is */
320
321         count = camel_folder_summary_count((CamelFolderSummary *)cls);
322         for (i=count-1;i>=0;i--) {
323                 info = (CamelLocalMessageInfo *)camel_folder_summary_index((CamelFolderSummary *)cls, i);
324                 g_assert(info);
325                 if (expunge && (info->info.flags & CAMEL_MESSAGE_DELETED)) {
326                         uid = camel_message_info_uid(info);
327                         name = g_strdup_printf("%s/%s", cls->folder_path, uid);
328                         d(printf("deleting %s\n", name));
329                         if (unlink(name) == 0 || errno==ENOENT) {
330
331                                 /* FIXME: put this in folder_summary::remove()? */
332                                 if (cls->index)
333                                         camel_index_delete_name(cls->index, (char *)uid);
334                                 
335                                 camel_folder_change_info_remove_uid(changes, uid);
336                                 camel_folder_summary_remove((CamelFolderSummary *)cls, (CamelMessageInfo *)info);
337                         }
338                         g_free(name);
339                 } else if (info->info.flags & (CAMEL_MESSAGE_FOLDER_NOXEV|CAMEL_MESSAGE_FOLDER_FLAGGED)) {
340                         info->info.flags &= 0xffff;
341                 }
342                 camel_message_info_free(info);
343         }
344
345         return ((CamelLocalSummaryClass *)parent_class)->sync(cls, expunge, changes, ex);
346 }