Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / local / camel-maildir-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
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
14  * GNU Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
19  * USA
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <dirent.h>
27 #include <errno.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32
33 #include <glib/gi18n-lib.h>
34
35 #include "camel-exception.h"
36 #include "camel-private.h"
37 #include "camel-url.h"
38
39 #include "camel-maildir-folder.h"
40 #include "camel-maildir-store.h"
41 #include "camel-maildir-summary.h"
42
43 #define d(x)
44
45 static CamelLocalStoreClass *parent_class = NULL;
46
47 /* Returns the class for a CamelMaildirStore */
48 #define CMAILDIRS_CLASS(so) CAMEL_MAILDIR_STORE_CLASS (CAMEL_OBJECT_GET_CLASS(so))
49 #define CF_CLASS(so) CAMEL_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
50 #define CMAILDIRF_CLASS(so) CAMEL_MAILDIR_FOLDER_CLASS (CAMEL_OBJECT_GET_CLASS(so))
51
52 static CamelFolder *get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex);
53 static CamelFolder *get_inbox (CamelStore *store, CamelException *ex);
54 static void delete_folder(CamelStore * store, const char *folder_name, CamelException * ex);
55 static void maildir_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
56
57 static CamelFolderInfo * get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex);
58
59 static gboolean maildir_compare_folder_name(const void *a, const void *b);
60 static guint maildir_hash_folder_name(const void *a);
61
62 static void camel_maildir_store_class_init(CamelObjectClass * camel_maildir_store_class)
63 {
64         CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_maildir_store_class);
65         /*CamelServiceClass *camel_service_class = CAMEL_SERVICE_CLASS(camel_maildir_store_class);*/
66
67         parent_class = (CamelLocalStoreClass *)camel_type_get_global_classfuncs(camel_local_store_get_type());
68
69         /* virtual method overload, use defaults for most */
70         camel_store_class->hash_folder_name = maildir_hash_folder_name;
71         camel_store_class->compare_folder_name = maildir_compare_folder_name;
72         camel_store_class->get_folder = get_folder;
73         camel_store_class->get_inbox = get_inbox;
74         camel_store_class->delete_folder = delete_folder;
75         camel_store_class->rename_folder = maildir_rename_folder;
76
77         camel_store_class->get_folder_info = get_folder_info;
78         camel_store_class->free_folder_info = camel_store_free_folder_info_full;
79 }
80
81 CamelType camel_maildir_store_get_type(void)
82 {
83         static CamelType camel_maildir_store_type = CAMEL_INVALID_TYPE;
84
85         if (camel_maildir_store_type == CAMEL_INVALID_TYPE) {
86                 camel_maildir_store_type = camel_type_register(CAMEL_LOCAL_STORE_TYPE, "CamelMaildirStore",
87                                                           sizeof(CamelMaildirStore),
88                                                           sizeof(CamelMaildirStoreClass),
89                                                           (CamelObjectClassInitFunc) camel_maildir_store_class_init,
90                                                           NULL,
91                                                           NULL,
92                                                           NULL);
93         }
94
95         return camel_maildir_store_type;
96 }
97
98 /* This fixes up some historical cruft of names starting with "./" */
99 static const char *
100 md_canon_name(const char *a)
101 {
102         if (a != NULL) {
103                 if (a[0] == '/')
104                         a++;
105                 if (a[0] == '.' && a[1] == '/')
106                         a+=2;
107         }
108         return a;
109 }
110
111 static guint maildir_hash_folder_name(const void *a)
112 {
113         return g_str_hash(md_canon_name(a));
114 }
115
116 static gboolean maildir_compare_folder_name(const void *a, const void *b)
117 {
118         return g_str_equal(md_canon_name(a), md_canon_name(b));
119 }
120
121 static CamelFolder *
122 get_folder(CamelStore * store, const char *folder_name, guint32 flags, CamelException * ex)
123 {
124         char *name, *tmp, *cur, *new;
125         struct stat st;
126         CamelFolder *folder = NULL;
127
128         folder_name = md_canon_name(folder_name);
129
130         if (!((CamelStoreClass *)parent_class)->get_folder(store, folder_name, flags, ex))
131                 return NULL;
132
133         name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
134         tmp = g_strdup_printf("%s/tmp", name);
135         cur = g_strdup_printf("%s/cur", name);
136         new = g_strdup_printf("%s/new", name);
137
138         if (!strcmp(folder_name, ".")) {
139                 /* special case "." (aka inbox), may need to be created */
140                 if (stat(tmp, &st) != 0 || !S_ISDIR(st.st_mode)
141                     || stat(cur, &st) != 0 || !S_ISDIR(st.st_mode)
142                     || stat(new, &st) != 0 || !S_ISDIR(st.st_mode)) {
143                         if (mkdir(tmp, 0700) != 0
144                             || mkdir(cur, 0700) != 0
145                             || mkdir(new, 0700) != 0) {
146                                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
147                                                      _("Cannot create folder `%s': %s"),
148                                                      folder_name, g_strerror(errno));
149                                 rmdir(tmp);
150                                 rmdir(cur);
151                                 rmdir(new);
152                                 goto fail;
153                         }
154                 }
155                 folder = camel_maildir_folder_new(store, folder_name, flags, ex);
156         } else if (stat(name, &st) == -1) {
157                 /* folder doesn't exist, see if we should create it */
158                 if (errno != ENOENT) {
159                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
160                                               _("Cannot get folder `%s': %s"),
161                                               folder_name, g_strerror (errno));
162                 } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
163                         camel_exception_setv (ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
164                                               _("Cannot get folder `%s': folder does not exist."),
165                                               folder_name);
166                 } else {
167                         if (mkdir(name, 0700) != 0
168                             || mkdir(tmp, 0700) != 0
169                             || mkdir(cur, 0700) != 0
170                             || mkdir(new, 0700) != 0) {
171                                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
172                                                       _("Cannot create folder `%s': %s"),
173                                                       folder_name, g_strerror (errno));
174                                 rmdir(tmp);
175                                 rmdir(cur);
176                                 rmdir(new);
177                                 rmdir(name);
178                         } else {
179                                 folder = camel_maildir_folder_new(store, folder_name, flags, ex);
180                         }
181                 }
182         } else if (!S_ISDIR(st.st_mode)
183                    || stat(tmp, &st) != 0 || !S_ISDIR(st.st_mode)
184                    || stat(cur, &st) != 0 || !S_ISDIR(st.st_mode)
185                    || stat(new, &st) != 0 || !S_ISDIR(st.st_mode)) {
186                 /* folder exists, but not maildir */
187                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
188                                      _("Cannot get folder `%s': not a maildir directory."), name);
189         } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
190                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
191                                       _("Cannot create folder `%s': folder exists."),
192                                       folder_name);
193         } else {
194                 folder = camel_maildir_folder_new(store, folder_name, flags, ex);
195         }
196 fail:
197         g_free(name);
198         g_free(tmp);
199         g_free(cur);
200         g_free(new);
201
202         return folder;
203 }
204
205 static CamelFolder *
206 get_inbox (CamelStore *store, CamelException *ex)
207 {
208         return camel_store_get_folder(store, ".", CAMEL_STORE_FOLDER_CREATE, ex);
209 }
210
211 static void delete_folder(CamelStore * store, const char *folder_name, CamelException * ex)
212 {
213         char *name, *tmp, *cur, *new;
214         struct stat st;
215
216         if (strcmp(folder_name, ".") == 0) {
217                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
218                                      _("Cannot delete folder: %s: Invalid operation"), _("Inbox"));
219                 return;
220         }
221
222         name = g_strdup_printf("%s%s", CAMEL_LOCAL_STORE(store)->toplevel_dir, folder_name);
223
224         tmp = g_strdup_printf("%s/tmp", name);
225         cur = g_strdup_printf("%s/cur", name);
226         new = g_strdup_printf("%s/new", name);
227
228         if (stat(name, &st) == -1 || !S_ISDIR(st.st_mode)
229             || stat(tmp, &st) == -1 || !S_ISDIR(st.st_mode)
230             || stat(cur, &st) == -1 || !S_ISDIR(st.st_mode)
231             || stat(new, &st) == -1 || !S_ISDIR(st.st_mode)) {
232                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
233                                       _("Could not delete folder `%s': %s"),
234                                       folder_name, errno ? g_strerror (errno) :
235                                       _("not a maildir directory"));
236         } else {
237                 int err = 0;
238
239                 /* remove subdirs first - will fail if not empty */
240                 if (rmdir(cur) == -1 || rmdir(new) == -1) {
241                         err = errno;
242                 } else {
243                         DIR *dir;
244                         struct dirent *d;
245
246                         /* for tmp (only), its contents is irrelevant */
247                         dir = opendir(tmp);
248                         if (dir) {
249                                 while ( (d=readdir(dir)) ) {
250                                         char *name = d->d_name, *file;
251
252                                         if (!strcmp(name, ".") || !strcmp(name, ".."))
253                                                 continue;
254                                         file = g_strdup_printf("%s/%s", tmp, name);
255                                         unlink(file);
256                                         g_free(file);
257                                 }
258                                 closedir(dir);
259                         }
260                         if (rmdir(tmp) == -1 || rmdir(name) == -1)
261                                 err = errno;
262                 }
263
264                 if (err != 0) {
265                         /* easier just to mkdir all (and let them fail), than remember what we got to */
266                         mkdir(name, 0700);
267                         mkdir(cur, 0700);
268                         mkdir(new, 0700);
269                         mkdir(tmp, 0700);
270                         camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
271                                               _("Could not delete folder `%s': %s"),
272                                               folder_name, g_strerror (err));
273                 } else {
274                         /* and remove metadata */
275                         ((CamelStoreClass *)parent_class)->delete_folder(store, folder_name, ex);
276                 }
277         }
278
279         g_free(name);
280         g_free(tmp);
281         g_free(cur);
282         g_free(new);
283 }
284
285 static void
286 maildir_rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
287 {
288         if (strcmp(old, ".") == 0) {
289                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
290                                      _("Cannot rename folder: %s: Invalid operation"), _("Inbox"));
291                 return;
292         }
293
294         ((CamelStoreClass *)parent_class)->rename_folder(store, old, new, ex);
295 }
296
297 static void
298 fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
299 {
300         CamelFolder *folder;
301
302         folder = camel_object_bag_get(store->folders, fi->full_name);
303
304         if (folder == NULL
305             && (flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
306                 folder = camel_store_get_folder(store, fi->full_name, 0, NULL);
307
308         if (folder) {
309                 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
310                         camel_folder_refresh_info(folder, NULL);
311                 fi->unread = camel_folder_get_unread_message_count(folder);
312                 fi->total = camel_folder_get_message_count(folder);
313                 camel_object_unref(folder);
314         } else {
315                 char *path, *folderpath;
316                 CamelFolderSummary *s;
317                 const char *root;
318
319                 /* This should be fast enough not to have to test for INFO_FAST */
320                 root = camel_local_store_get_toplevel_dir((CamelLocalStore *)store);
321                 path = g_strdup_printf("%s/%s.ev-summary", root, fi->full_name);
322                 folderpath = g_strdup_printf("%s/%s", root, fi->full_name);
323                 s = (CamelFolderSummary *)camel_maildir_summary_new(NULL, path, folderpath, NULL);
324                 if (camel_folder_summary_header_load(s) != -1) {
325                         fi->unread = s->unread_count;
326                         fi->total = s->saved_count;
327                 }
328                 camel_object_unref(s);
329                 g_free(folderpath);
330                 g_free(path);
331         }
332 }
333
334 struct _scan_node {
335         struct _scan_node *next;
336         struct _scan_node *prev;
337
338         CamelFolderInfo *fi;
339
340         dev_t dnode;
341         ino_t inode;
342 };
343
344 static guint scan_hash(const void *d)
345 {
346         const struct _scan_node *v = d;
347
348         return v->inode ^ v->dnode;
349 }
350
351 static gboolean scan_equal(const void *a, const void *b)
352 {
353         const struct _scan_node *v1 = a, *v2 = b;
354         
355         return v1->inode == v2->inode && v1->dnode == v2->dnode;
356 }
357
358 static void scan_free(void *k, void *v, void *d)
359 {
360         g_free(k);
361 }
362
363 static CamelFolderInfo *scan_fi(CamelStore *store, guint32 flags, CamelURL *url, const char *full, const char *name)
364 {
365         CamelFolderInfo *fi;
366         char *tmp, *cur, *new;
367         struct stat st;
368
369         fi = g_malloc0(sizeof(*fi));
370         fi->full_name = g_strdup(full);
371         fi->name = g_strdup(name);
372         camel_url_set_fragment(url, fi->full_name);
373         fi->uri = camel_url_to_string(url, 0);
374         fi->unread = -1;
375         fi->total = -1;
376         /* we only calculate nochildren properly if we're recursive */
377         if (((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) != 0))
378                 fi->flags = CAMEL_FOLDER_NOCHILDREN;
379
380         d(printf("Adding maildir info: '%s' '%s' '%s'\n", fi->name, fi->full_name, fi->uri));
381
382         tmp = g_build_filename(url->path, fi->full_name, "tmp", NULL);
383         cur = g_build_filename(url->path, fi->full_name, "cur", NULL);
384         new = g_build_filename(url->path, fi->full_name, "new", NULL);
385
386         if (!(stat(tmp, &st) == 0 && S_ISDIR(st.st_mode)
387               && stat(cur, &st) == 0 && S_ISDIR(st.st_mode)
388               && stat(new, &st) == 0 && S_ISDIR(st.st_mode)))
389                 fi->flags |= CAMEL_FOLDER_NOSELECT;
390
391         g_free(new);
392         g_free(cur);
393         g_free(tmp);
394
395         fill_fi(store, fi, flags);
396
397         return fi;
398 }
399
400 static int
401 scan_dirs(CamelStore *store, guint32 flags, CamelFolderInfo *topfi, CamelURL *url, CamelException *ex)
402 {
403         EDList queue = E_DLIST_INITIALISER(queue);
404         struct _scan_node *sn;
405         const char *root = ((CamelService *)store)->url->path;
406         char *tmp;
407         GHashTable *visited;
408         struct stat st;
409         int res = -1;
410
411         visited = g_hash_table_new(scan_hash, scan_equal);
412
413         sn = g_malloc0(sizeof(*sn));
414         sn->fi = topfi;
415         e_dlist_addtail(&queue, (EDListNode *)sn);
416         g_hash_table_insert(visited, sn, sn);
417
418         while (!e_dlist_empty(&queue)) {
419                 char *name;
420                 DIR *dir;
421                 struct dirent *d;
422                 CamelFolderInfo *last;
423
424                 sn = (struct _scan_node *)e_dlist_remhead(&queue);
425
426                 last = (CamelFolderInfo *)&sn->fi->child;
427
428                 if (!strcmp(sn->fi->full_name, "."))
429                         name = g_strdup(root);
430                 else
431                         name = g_build_filename(root, sn->fi->full_name, NULL);
432
433                 dir = opendir(name);
434                 if (dir == NULL) {
435                         g_free(name);
436                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
437                                              _("Could not scan folder `%s': %s"),
438                                              root, g_strerror(errno));
439                         goto fail;
440                 }
441
442                 while ( (d = readdir(dir)) ) {
443                         if (strcmp(d->d_name, "tmp") == 0
444                             || strcmp(d->d_name, "cur") == 0
445                             || strcmp(d->d_name, "new") == 0
446                             || strcmp(d->d_name, ".") == 0
447                             || strcmp(d->d_name, "..") == 0)
448                                 continue;
449
450                         tmp = g_build_filename(name, d->d_name, NULL);
451                         if (stat(tmp, &st) == 0 && S_ISDIR(st.st_mode)) {
452                                 struct _scan_node in;
453
454                                 in.dnode = st.st_dev;
455                                 in.inode = st.st_ino;
456
457                                 /* see if we've visited already */
458                                 if (g_hash_table_lookup(visited, &in) == NULL) {
459                                         struct _scan_node *snew = g_malloc(sizeof(*snew));
460                                         char *full;
461
462                                         snew->dnode = in.dnode;
463                                         snew->inode = in.inode;
464
465                                         if (!strcmp(sn->fi->full_name, "."))
466                                                 full = g_strdup(d->d_name);
467                                         else
468                                                 full = g_strdup_printf("%s/%s", sn->fi->full_name, d->d_name);
469                                         snew->fi = scan_fi(store, flags, url, full, d->d_name);
470                                         g_free(full);
471
472                                         last->next =  snew->fi;
473                                         last = snew->fi;
474                                         snew->fi->parent = sn->fi;
475
476                                         sn->fi->flags &= ~CAMEL_FOLDER_NOCHILDREN;
477                                         sn->fi->flags |= CAMEL_FOLDER_CHILDREN;
478
479                                         g_hash_table_insert(visited, snew, snew);
480
481                                         if (((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) != 0))
482                                                 e_dlist_addtail(&queue, (EDListNode *)snew);
483                                 }
484                         }
485                         g_free(tmp);
486                 }
487                 closedir(dir);
488         }
489
490         res = 0;
491 fail:
492         g_hash_table_foreach(visited, scan_free, NULL);
493         g_hash_table_destroy(visited);
494
495         return res;
496 }
497
498 static CamelFolderInfo *
499 get_folder_info (CamelStore *store, const char *top, guint32 flags, CamelException *ex)
500 {
501         CamelFolderInfo *fi = NULL;
502         CamelLocalStore *local_store = (CamelLocalStore *)store;
503         CamelURL *url;
504
505         url = camel_url_new("maildir:", NULL);
506         camel_url_set_path(url, ((CamelService *)local_store)->url->path);
507
508         if (top == NULL || top[0] == 0) {
509                 CamelFolderInfo *scan;
510
511                 /* create a dummy "." parent inbox, use to scan, then put back at the top level */
512                 fi = scan_fi(store, flags, url, ".", _("Inbox"));
513                 if (scan_dirs(store, flags, fi, url, ex) == -1)
514                         goto fail;
515                 fi->next = fi->child;
516                 scan = fi->child;
517                 fi->child = NULL;
518                 while (scan) {
519                         scan->parent = NULL;
520                         scan = scan->next;
521                 }
522                 fi->flags &= ~CAMEL_FOLDER_CHILDREN;
523                 fi->flags |= CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_TYPE_INBOX;
524         } else if (!strcmp(top, ".")) {
525                 fi = scan_fi(store, flags, url, ".", _("Inbox"));
526                 fi->flags |= CAMEL_FOLDER_SYSTEM|CAMEL_FOLDER_NOCHILDREN|CAMEL_FOLDER_NOINFERIORS|CAMEL_FOLDER_TYPE_INBOX;
527         } else {
528                 const char *name = strrchr(top, '/');
529
530                 fi = scan_fi(store, flags, url, top, name?name+1:top);
531                 if (scan_dirs(store, flags, fi, url, ex) == -1)
532                         goto fail;
533         }
534
535         camel_url_free(url);
536
537         return fi;
538
539 fail:
540         if (fi)
541                 camel_store_free_folder_info_full(store, fi);
542
543         camel_url_free(url);
544
545         return NULL;
546 }