Whitespace cleanups.
[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) 1999-2008 Novell, Inc. (www.novell.com)
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/gstdio.h>
34 #include <glib/gi18n-lib.h>
35
36 #include "camel-maildir-folder.h"
37 #include "camel-maildir-store.h"
38 #include "camel-maildir-summary.h"
39
40 #define d(x)
41
42 G_DEFINE_TYPE (CamelMaildirStore, camel_maildir_store, CAMEL_TYPE_LOCAL_STORE)
43
44 #define HIER_SEP "."
45 #define HIER_SEP_CHAR '.'
46
47 static CamelFolder * maildir_store_get_folder_sync (CamelStore *store, const gchar *folder_name, CamelStoreGetFolderFlags flags,
48                                                 GCancellable *cancellable, GError **error);
49 static CamelFolderInfo *maildir_store_create_folder_sync (CamelStore *store, const gchar *parent_name, const gchar *folder_name,
50                                                 GCancellable *cancellable, GError **error);
51 static gboolean maildir_store_delete_folder_sync (CamelStore * store, const gchar *folder_name, GCancellable *cancellable, GError **error);
52
53 static gchar *maildir_full_name_to_dir_name (const gchar *full_name);
54 static gchar *maildir_dir_name_to_fullname (const gchar *dir_name);
55 static gchar *maildir_get_full_path (CamelLocalStore *ls, const gchar *full_name);
56 static gchar *maildir_get_meta_path (CamelLocalStore *ls, const gchar *full_name, const gchar *ext);
57 static void maildir_migrate_hierarchy (CamelMaildirStore *mstore, GCancellable *cancellable, GError **error);
58
59 /* This fixes up some historical cruft of names starting with "./" */
60 static const gchar *
61 md_canon_name (const gchar *a)
62 {
63         if (a != NULL) {
64                 if (a[0] == '/')
65                         a++;
66                 if (a[0] == '.' && a[1] == '/')
67                         a+=2;
68         }
69
70         return a;
71 }
72
73 static CamelFolderInfo *
74 maildir_store_create_folder_sync (CamelStore *store,
75                                   const gchar *parent_name,
76                                   const gchar *folder_name,
77                                   GCancellable *cancellable,
78                                   GError **error)
79 {
80         CamelLocalSettings *local_settings;
81         CamelSettings *settings;
82         CamelService *service;
83         CamelFolder *folder;
84         CamelFolderInfo *info = NULL;
85         gchar *name = NULL;
86         gchar *path;
87         struct stat st;
88
89         /* This is a pretty hacky version of create folder, but should basically work */
90
91         service = CAMEL_SERVICE (store);
92         settings = camel_service_get_settings (service);
93
94         local_settings = CAMEL_LOCAL_SETTINGS (settings);
95         path = camel_local_settings_dup_path (local_settings);
96
97         if (!g_path_is_absolute (path)) {
98                 g_set_error (
99                         error, CAMEL_STORE_ERROR,
100                         CAMEL_STORE_ERROR_NO_FOLDER,
101                         _("Store root %s is not an absolute path"), path);
102                 goto exit;
103         }
104
105         if (g_strstr_len (folder_name, -1, ".")) {
106                 g_set_error (
107                         error, CAMEL_STORE_ERROR,
108                         CAMEL_STORE_ERROR_NO_FOLDER,
109                         _("Cannot create folder: %s: "
110                           "Folder name cannot contain a dot"),
111                         folder_name);
112                 goto exit;
113         }
114
115         if (!g_ascii_strcasecmp (folder_name, "Inbox")) {
116                 g_set_error (
117                         error, CAMEL_STORE_ERROR,
118                         CAMEL_STORE_ERROR_NO_FOLDER,
119                         _("Folder %s already exists"), folder_name);
120                 goto exit;
121         }
122
123         if (parent_name && *parent_name) {
124                 gchar *dir_name = maildir_full_name_to_dir_name (parent_name);
125                 name = g_strdup_printf("%s/%s.%s", path, dir_name, folder_name);
126                 g_free (dir_name);
127         } else
128                 name = maildir_full_name_to_dir_name (folder_name);
129
130         if (g_stat (name, &st) == 0 || errno != ENOENT) {
131                 g_set_error (
132                         error, G_IO_ERROR,
133                         g_io_error_from_errno (errno),
134                         _("Cannot get folder: %s: %s"),
135                         name, g_strerror (errno));
136                 goto exit;
137         }
138
139         g_free (name);
140         name = NULL;
141
142         if (parent_name && *parent_name)
143                 name = g_strdup_printf("%s/%s", parent_name, folder_name);
144         else
145                 name = g_strdup_printf("%s", folder_name);
146
147         folder = maildir_store_get_folder_sync (
148                 store, name, CAMEL_STORE_FOLDER_CREATE, cancellable, error);
149         if (folder) {
150                 g_object_unref (folder);
151                 info = CAMEL_STORE_GET_CLASS (store)->get_folder_info_sync (
152                         store, name, 0, cancellable, error);
153         }
154
155 exit:
156         g_free (name);
157         g_free (path);
158
159         return info;
160 }
161
162 static CamelFolder *
163 maildir_store_get_folder_sync (CamelStore *store,
164                                const gchar *folder_name,
165                                CamelStoreGetFolderFlags flags,
166                                GCancellable *cancellable,
167                                GError **error)
168 {
169         CamelStoreClass *store_class;
170         CamelLocalSettings *local_settings;
171         CamelSettings *settings;
172         CamelService *service;
173         gchar *name, *tmp, *cur, *new, *dir_name;
174         gchar *path;
175         struct stat st;
176         CamelFolder *folder = NULL;
177
178         service = CAMEL_SERVICE (store);
179         settings = camel_service_get_settings (service);
180
181         local_settings = CAMEL_LOCAL_SETTINGS (settings);
182         path = camel_local_settings_dup_path (local_settings);
183
184         folder_name = md_canon_name (folder_name);
185         dir_name = maildir_full_name_to_dir_name (folder_name);
186
187         /* maildir++ directory names start with a '.' */
188         name = g_build_filename (path, dir_name, NULL);
189
190         g_free (dir_name);
191         g_free (path);
192
193         /* Chain up to parent's get_folder() method. */
194         store_class = CAMEL_STORE_CLASS (camel_maildir_store_parent_class);
195         if (!store_class->get_folder_sync (store, dir_name, flags, cancellable, error)) {
196                 g_free (name);
197                 return NULL;
198         }
199
200         tmp = g_strdup_printf ("%s/tmp", name);
201         cur = g_strdup_printf ("%s/cur", name);
202         new = g_strdup_printf ("%s/new", name);
203
204         if (!g_ascii_strcasecmp (folder_name, "Inbox")) {
205                 /* special case "." (aka inbox), may need to be created */
206                 if (g_stat (tmp, &st) != 0 || !S_ISDIR (st.st_mode)
207                     || g_stat (cur, &st) != 0 || !S_ISDIR (st.st_mode)
208                     || g_stat (new, &st) != 0 || !S_ISDIR (st.st_mode)) {
209                         if (g_mkdir (tmp, 0700) != 0
210                             || g_mkdir (cur, 0700) != 0
211                             || g_mkdir (new, 0700) != 0) {
212                                 g_set_error (
213                                         error, G_IO_ERROR,
214                                         g_io_error_from_errno (errno),
215                                         _("Cannot create folder '%s': %s"),
216                                         folder_name, g_strerror (errno));
217                                 rmdir (tmp);
218                                 rmdir (cur);
219                                 rmdir (new);
220                                 goto fail;
221                         }
222                 }
223                 folder = camel_maildir_folder_new (store, folder_name, flags, cancellable, error);
224         } else if (g_stat (name, &st) == -1) {
225                 /* folder doesn't exist, see if we should create it */
226                 if (errno != ENOENT) {
227                         g_set_error (
228                                 error, G_IO_ERROR,
229                                 g_io_error_from_errno (errno),
230                                 _("Cannot get folder '%s': %s"),
231                                 folder_name, g_strerror (errno));
232                 } else if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
233                         g_set_error (
234                                 error, CAMEL_STORE_ERROR,
235                                 CAMEL_STORE_ERROR_NO_FOLDER,
236                                 _("Cannot get folder '%s': folder does not exist."),
237                                 folder_name);
238                 } else {
239                         if (g_mkdir (name, 0700) != 0
240                             || g_mkdir (tmp, 0700) != 0
241                             || g_mkdir (cur, 0700) != 0
242                             || g_mkdir (new, 0700) != 0) {
243                                 g_set_error (
244                                         error, G_IO_ERROR,
245                                         g_io_error_from_errno (errno),
246                                         _("Cannot create folder '%s': %s"),
247                                         folder_name, g_strerror (errno));
248                                 rmdir (tmp);
249                                 rmdir (cur);
250                                 rmdir (new);
251                                 rmdir (name);
252                         } else {
253                                 folder = camel_maildir_folder_new (store, folder_name, flags, cancellable, error);
254                         }
255                 }
256         } else if (!S_ISDIR (st.st_mode)
257                    || g_stat (tmp, &st) != 0 || !S_ISDIR (st.st_mode)
258                    || g_stat (cur, &st) != 0 || !S_ISDIR (st.st_mode)
259                    || g_stat (new, &st) != 0 || !S_ISDIR (st.st_mode)) {
260                 /* folder exists, but not maildir */
261                 g_set_error (
262                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
263                         _("Cannot get folder '%s': not a maildir directory."),
264                         name);
265         } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
266                 g_set_error (
267                         error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
268                         _("Cannot create folder '%s': folder exists."),
269                         folder_name);
270         } else {
271                 folder = camel_maildir_folder_new (store, folder_name, flags, cancellable, error);
272         }
273 fail:
274         g_free (name);
275         g_free (tmp);
276         g_free (cur);
277         g_free (new);
278
279         return folder;
280 }
281
282 static gboolean
283 maildir_store_delete_folder_sync (CamelStore *store,
284                                   const gchar *folder_name,
285                                   GCancellable *cancellable,
286                                   GError **error)
287 {
288         CamelLocalSettings *local_settings;
289         CamelSettings *settings;
290         CamelService *service;
291         gchar *name, *tmp, *cur, *new, *dir_name;
292         gchar *path;
293         struct stat st;
294         gboolean success = TRUE;
295
296         if (g_ascii_strcasecmp (folder_name, "Inbox") == 0) {
297                 g_set_error (
298                         error, CAMEL_STORE_ERROR,
299                         CAMEL_STORE_ERROR_NO_FOLDER,
300                         _("Cannot delete folder: %s: Invalid operation"),
301                         _("Inbox"));
302                 return FALSE;
303         }
304
305         service = CAMEL_SERVICE (store);
306         settings = camel_service_get_settings (service);
307
308         local_settings = CAMEL_LOCAL_SETTINGS (settings);
309         path = camel_local_settings_dup_path (local_settings);
310
311         /* maildir++ directory names start with a '.' */
312         dir_name = maildir_full_name_to_dir_name (folder_name);
313         name = g_build_filename (path, dir_name, NULL);
314         g_free (dir_name);
315
316         g_free (path);
317
318         tmp = g_strdup_printf ("%s/tmp", name);
319         cur = g_strdup_printf ("%s/cur", name);
320         new = g_strdup_printf ("%s/new", name);
321
322         if (g_stat (name, &st) == -1 || !S_ISDIR (st.st_mode)
323             || g_stat (tmp, &st) == -1 || !S_ISDIR (st.st_mode)
324             || g_stat (cur, &st) == -1 || !S_ISDIR (st.st_mode)
325             || g_stat (new, &st) == -1 || !S_ISDIR (st.st_mode)) {
326                 g_set_error (
327                         error, G_IO_ERROR,
328                         g_io_error_from_errno (errno),
329                         _("Could not delete folder '%s': %s"),
330                         folder_name, errno ? g_strerror (errno) :
331                         _("not a maildir directory"));
332         } else {
333                 gint err = 0;
334
335                 /* remove subdirs first - will fail if not empty */
336                 if (rmdir (cur) == -1 || rmdir (new) == -1) {
337                         err = errno;
338                 } else {
339                         DIR *dir;
340                         struct dirent *d;
341
342                         /* for tmp (only), its contents is irrelevant */
343                         dir = opendir (tmp);
344                         if (dir) {
345                                 while ((d = readdir (dir))) {
346                                         gchar *name = d->d_name, *file;
347
348                                         if (!strcmp(name, ".") || !strcmp(name, ".."))
349                                                 continue;
350                                         file = g_strdup_printf("%s/%s", tmp, name);
351                                         unlink (file);
352                                         g_free (file);
353                                 }
354                                 closedir (dir);
355                         }
356                         if (rmdir (tmp) == -1 || rmdir (name) == -1)
357                                 err = errno;
358                 }
359
360                 if (err != 0) {
361                         /* easier just to mkdir all (and let them fail), than remember what we got to */
362                         g_mkdir (name, 0700);
363                         g_mkdir (cur, 0700);
364                         g_mkdir (new, 0700);
365                         g_mkdir (tmp, 0700);
366                         g_set_error (
367                                 error, G_IO_ERROR,
368                                 g_io_error_from_errno (err),
369                                 _("Could not delete folder '%s': %s"),
370                                 folder_name, g_strerror (err));
371                 } else {
372                         CamelStoreClass *store_class;
373
374                         /* Chain up to parent's delete_folder() method. */
375                         store_class = CAMEL_STORE_CLASS (camel_maildir_store_parent_class);
376                         success = store_class->delete_folder_sync (
377                                 store, folder_name, cancellable, error);
378                 }
379         }
380
381         g_free (name);
382         g_free (tmp);
383         g_free (cur);
384         g_free (new);
385
386         return success;
387 }
388
389 static void
390 fill_fi (CamelStore *store,
391          CamelFolderInfo *fi,
392          guint32 flags,
393          GCancellable *cancellable)
394 {
395         CamelFolder *folder;
396
397         folder = camel_object_bag_peek (store->folders, fi->full_name);
398         if (folder) {
399                 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
400                         camel_folder_refresh_info_sync (folder, cancellable, NULL);
401                 fi->unread = camel_folder_get_unread_message_count (folder);
402                 fi->total = camel_folder_get_message_count (folder);
403                 g_object_unref (folder);
404         } else {
405                 CamelLocalSettings *local_settings;
406                 CamelSettings *settings;
407                 CamelService *service;
408                 gchar *folderpath, *dir_name;
409                 CamelFolderSummary *s;
410                 gchar *root;
411
412                 service = CAMEL_SERVICE (store);
413                 settings = camel_service_get_settings (service);
414
415                 local_settings = CAMEL_LOCAL_SETTINGS (settings);
416                 root = camel_local_settings_dup_path (local_settings);
417
418                 /* This should be fast enough not to have to test for INFO_FAST */
419                 dir_name = maildir_full_name_to_dir_name (fi->full_name);
420
421                 if (!strcmp (dir_name, "."))
422                         folderpath = g_strdup (root);
423                 else
424                         folderpath = g_build_filename (root, dir_name, NULL);
425
426                 g_free (root);
427
428                 s = (CamelFolderSummary *) camel_maildir_summary_new (NULL, folderpath, NULL);
429                 if (camel_folder_summary_header_load_from_db (s, store, fi->full_name, NULL)) {
430                         fi->unread = camel_folder_summary_get_unread_count (s);
431                         fi->total = camel_folder_summary_get_saved_count (s);
432                 }
433                 g_object_unref (s);
434                 g_free (folderpath);
435                 g_free (dir_name);
436         }
437
438         if (camel_local_store_is_main_store (CAMEL_LOCAL_STORE (store)) && fi->full_name
439             && (fi->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_NORMAL)
440                 fi->flags = (fi->flags & ~CAMEL_FOLDER_TYPE_MASK)
441                             | camel_local_store_get_folder_type_by_full_name (CAMEL_LOCAL_STORE (store), fi->full_name);
442 }
443
444 static CamelFolderInfo *
445 scan_fi (CamelStore *store,
446          guint32 flags,
447          const gchar *full,
448          const gchar *name,
449          GCancellable *cancellable)
450 {
451         CamelLocalSettings *local_settings;
452         CamelSettings *settings;
453         CamelService *service;
454         CamelFolderInfo *fi;
455         gchar *tmp, *cur, *new, *dir_name;
456         gchar *path;
457         struct stat st;
458
459         service = CAMEL_SERVICE (store);
460         settings = camel_service_get_settings (service);
461
462         local_settings = CAMEL_LOCAL_SETTINGS (settings);
463         path = camel_local_settings_dup_path (local_settings);
464         g_return_val_if_fail (path != NULL, NULL);
465
466         fi = camel_folder_info_new ();
467         fi->full_name = g_strdup (full);
468         fi->display_name = g_strdup (name);
469
470         fi->unread = -1;
471         fi->total = -1;
472
473         /* we only calculate nochildren properly if we're recursive */
474         if (((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) != 0))
475                 fi->flags = CAMEL_FOLDER_NOCHILDREN;
476
477         dir_name = maildir_full_name_to_dir_name (fi->full_name);
478         d(printf("Adding maildir info: '%s' '%s' '%s'\n", fi->name, dir_name, fi->uri));
479
480         tmp = g_build_filename (path, dir_name, "tmp", NULL);
481         cur = g_build_filename (path, dir_name, "cur", NULL);
482         new = g_build_filename (path, dir_name, "new", NULL);
483
484         if (!(g_stat (tmp, &st) == 0 && S_ISDIR (st.st_mode)
485               && g_stat (cur, &st) == 0 && S_ISDIR (st.st_mode)
486               && g_stat (new, &st) == 0 && S_ISDIR (st.st_mode)))
487                 fi->flags |= CAMEL_FOLDER_NOSELECT;
488
489         g_free (new);
490         g_free (cur);
491         g_free (tmp);
492         g_free (dir_name);
493
494         fill_fi (store, fi, flags, cancellable);
495
496         g_free (path);
497
498         return fi;
499 }
500
501 /* Folder names begin with a dot */
502 static gchar *
503 maildir_full_name_to_dir_name (const gchar *full_name)
504 {
505         gchar *path;
506
507         if (g_ascii_strcasecmp (full_name, "Inbox")) {
508                 if (!g_ascii_strncasecmp (full_name, "Inbox/", 6))
509                         path = g_strconcat (".", full_name + 5, NULL);
510                 else
511                         path = g_strconcat (".", full_name, NULL);
512
513                 g_strdelimit (path + 1, "/", HIER_SEP_CHAR);
514         } else
515                 path = g_strdup (".");
516
517         return path;
518 }
519
520 static gchar *
521 maildir_dir_name_to_fullname (const gchar *dir_name)
522 {
523         gchar *full_name;
524
525         if (!g_ascii_strncasecmp (dir_name, "..", 2))
526                 full_name = g_strconcat ("Inbox/", dir_name + 2, NULL);
527         else
528                 full_name = g_strdup (dir_name + 1);
529
530         g_strdelimit (full_name, HIER_SEP, '/');
531
532         return full_name;
533 }
534
535 static gint
536 scan_dirs (CamelStore *store,
537            guint32 flags,
538            gboolean can_inbox_sibling,
539            CamelFolderInfo **topfi,
540            GCancellable *cancellable,
541            GError **error)
542 {
543         CamelLocalSettings *local_settings;
544         CamelSettings *settings;
545         CamelService *service;
546         GPtrArray *folders;
547         gint res = -1;
548         DIR *dir;
549         struct dirent *d;
550         gchar *meta_path = NULL;
551         gchar *path;
552
553         service = CAMEL_SERVICE (store);
554         settings = camel_service_get_settings (service);
555
556         local_settings = CAMEL_LOCAL_SETTINGS (settings);
557         path = camel_local_settings_dup_path (local_settings);
558         g_return_val_if_fail (path != NULL, -1);
559
560         folders = g_ptr_array_new ();
561         if (!g_ascii_strcasecmp ((*topfi)->full_name, "Inbox"))
562                 g_ptr_array_add (folders, (*topfi));
563
564         dir = opendir (path);
565         if (dir == NULL) {
566                 g_set_error (
567                         error, G_IO_ERROR,
568                         g_io_error_from_errno (errno),
569                         _("Could not scan folder '%s': %s"),
570                         path, g_strerror (errno));
571                 goto exit;
572         }
573
574         meta_path = maildir_get_meta_path ((CamelLocalStore *) store, ".", "maildir++");
575         if (!g_file_test (meta_path, G_FILE_TEST_EXISTS))
576                 maildir_migrate_hierarchy ((CamelMaildirStore *) store, cancellable, error);
577
578         g_free (meta_path);
579
580         while ((d = readdir (dir))) {
581                 gchar *full_name, *filename;
582                 const gchar *short_name;
583                 CamelFolderInfo *fi;
584                 struct stat st;
585
586                 if (strcmp(d->d_name, "tmp") == 0
587                                 || strcmp(d->d_name, "cur") == 0
588                                 || strcmp(d->d_name, "new") == 0
589                                 || strcmp(d->d_name, ".#evolution") == 0
590                                 || strcmp(d->d_name, ".") == 0
591                                 || strcmp(d->d_name, "..") == 0
592                                 || !g_str_has_prefix (d->d_name, "."))
593
594                                 continue;
595
596                 filename = g_build_filename (path, d->d_name, NULL);
597                 if (!(g_stat (filename, &st) == 0 && S_ISDIR (st.st_mode))) {
598                         g_free (filename);
599                         continue;
600                 }
601                 g_free (filename);
602                 full_name = maildir_dir_name_to_fullname (d->d_name);
603                 short_name = strrchr (full_name, '/');
604                 if (!short_name)
605                         short_name = full_name;
606                 else
607                         short_name++;
608
609                 if ((g_ascii_strcasecmp ((*topfi)->full_name, "Inbox") != 0 
610                     && (!g_str_has_prefix (full_name, (*topfi)->full_name) ||
611                         (full_name[strlen ((*topfi)->full_name)] != '\0' &&
612                          full_name[strlen ((*topfi)->full_name)] != '/')))
613                     || (!can_inbox_sibling
614                     && g_ascii_strcasecmp ((*topfi)->full_name, "Inbox") == 0 
615                     && (!g_str_has_prefix (full_name, (*topfi)->full_name) ||
616                         (full_name[strlen ((*topfi)->full_name)] != '\0' &&
617                          full_name[strlen ((*topfi)->full_name)] != '/')))) {
618                         g_free (full_name);
619                         continue;
620                 }
621
622                 fi = scan_fi (store, flags, full_name, short_name, cancellable);
623                 g_free (full_name);
624
625                 fi->flags &= ~CAMEL_FOLDER_NOCHILDREN;
626                 fi->flags |= CAMEL_FOLDER_CHILDREN;
627
628                 g_ptr_array_add (folders, fi);
629         }
630
631         closedir (dir);
632
633         if (folders->len != 0) {
634                 if (!g_ascii_strcasecmp ((*topfi)->full_name, "Inbox")) {
635                         *topfi = camel_folder_info_build (folders, "", '/', TRUE);
636                 } else {
637                         CamelFolderInfo *old_topfi = *topfi;
638
639                         *topfi = camel_folder_info_build (folders, (*topfi)->full_name, '/', TRUE);
640                         camel_store_free_folder_info (store, old_topfi);
641                 }
642
643                 res = 0;
644         } else
645                 res = -1;
646
647 exit:
648         g_ptr_array_free (folders, TRUE);
649
650         g_free (path);
651
652         return res;
653 }
654
655 static guint
656 maildir_store_hash_folder_name (gconstpointer a)
657 {
658         return g_str_hash (md_canon_name (a));
659 }
660
661 static gboolean
662 maildir_store_compare_folder_name (gconstpointer a,
663                                    gconstpointer b)
664 {
665         return g_str_equal (md_canon_name (a), md_canon_name (b));
666 }
667
668 static CamelFolderInfo *
669 maildir_store_get_folder_info_sync (CamelStore *store,
670                                     const gchar *top,
671                                     CamelStoreGetFolderInfoFlags flags,
672                                     GCancellable *cancellable,
673                                     GError **error)
674 {
675         CamelFolderInfo *fi = NULL;
676
677         if (top == NULL || top[0] == 0) {
678                 /* create a dummy "." parent inbox, use to scan, then put back at the top level */
679                 fi = scan_fi(store, flags, "Inbox", _("Inbox"), cancellable);
680                 if (scan_dirs (store, flags, TRUE, &fi, cancellable, error) == -1)
681                         goto fail;
682
683                 fi->flags |= CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_TYPE_INBOX;
684         } else if (!strcmp(top, ".")) {
685                 fi = scan_fi(store, flags, "Inbox", _("Inbox"), cancellable);
686                 fi->flags |= CAMEL_FOLDER_SYSTEM | CAMEL_FOLDER_TYPE_INBOX;
687         } else {
688                 const gchar *name = strrchr (top, '/');
689
690                 fi = scan_fi (store, flags, top, name ? name + 1 : top, cancellable);
691                 if (g_strcmp0 (fi->full_name, CAMEL_VTRASH_NAME) != 0 &&
692                     g_strcmp0 (fi->full_name, CAMEL_VJUNK_NAME) != 0 &&
693                     scan_dirs (store, flags, FALSE, &fi, cancellable, error) == -1)
694                         goto fail;
695         }
696
697         return fi;
698
699 fail:
700         if (fi)
701                 camel_store_free_folder_info_full (store, fi);
702
703         return NULL;
704 }
705
706 static CamelFolder *
707 maildir_store_get_inbox_sync (CamelStore *store,
708                               GCancellable *cancellable,
709                               GError **error)
710 {
711         return camel_store_get_folder_sync (
712                 store, "Inbox", CAMEL_STORE_FOLDER_CREATE, cancellable, error);
713 }
714
715 static gboolean
716 rename_traverse_fi (CamelStore *store,
717                     CamelStoreClass *store_class,
718                     CamelFolderInfo *fi,
719                     const gchar *old_full_name_prefix,
720                     const gchar *new_full_name_prefix,
721                     GCancellable *cancellable,
722                     GError **error)
723 {
724         gint old_prefix_len = strlen (old_full_name_prefix);
725         gboolean ret = TRUE;
726
727         while (fi && ret) {
728                 if (fi->full_name && g_str_has_prefix (fi->full_name, old_full_name_prefix)) {
729                         gchar *new_full_name, *old_dir, *new_dir;
730
731                         new_full_name = g_strconcat (new_full_name_prefix, fi->full_name + old_prefix_len, NULL);
732                         old_dir = maildir_full_name_to_dir_name (fi->full_name);
733                         new_dir = maildir_full_name_to_dir_name (new_full_name);
734
735                         /* Chain up to parent's rename_folder_sync() method. */
736                         ret = store_class->rename_folder_sync (store, old_dir, new_dir, cancellable, error);
737
738                         g_free (old_dir);
739                         g_free (new_dir);
740                         g_free (new_full_name);
741                 }
742
743                 if (fi->child && !rename_traverse_fi (store, store_class, fi->child, old_full_name_prefix, new_full_name_prefix, cancellable, error))
744                         return FALSE;
745
746                 fi = fi->next;
747         }
748
749         return ret;
750 }
751
752 static gboolean
753 maildir_store_rename_folder_sync (CamelStore *store,
754                                   const gchar *old,
755                                   const gchar *new,
756                                   GCancellable *cancellable,
757                                   GError **error)
758 {
759         CamelStoreClass *store_class;
760         gboolean ret;
761         gchar *old_dir, *new_dir;
762         CamelFolderInfo *subfolders;
763
764         if (strcmp(old, ".") == 0) {
765                 g_set_error (
766                         error, CAMEL_STORE_ERROR,
767                         CAMEL_STORE_ERROR_NO_FOLDER,
768                         _("Cannot rename folder: %s: Invalid operation"),
769                         _("Inbox"));
770                 return FALSE;
771         }
772
773         if (g_strstr_len (new, -1, ".")) {
774                 g_set_error (
775                         error, CAMEL_STORE_ERROR,
776                         CAMEL_STORE_ERROR_NO_FOLDER,
777                         _("Cannot rename the folder: %s: Folder name cannot contain a dot"), new);
778                 return FALSE;
779
780         }
781
782         if (!g_ascii_strcasecmp (new, "Inbox")) {
783                 g_set_error (
784                         error, CAMEL_STORE_ERROR,
785                         CAMEL_STORE_ERROR_NO_FOLDER,
786                         _("Folder %s already exists"), new);
787                 return FALSE;
788         }
789
790         subfolders = maildir_store_get_folder_info_sync (store, old, CAMEL_STORE_FOLDER_INFO_RECURSIVE | CAMEL_STORE_FOLDER_INFO_NO_VIRTUAL, cancellable, NULL);
791
792         old_dir = maildir_full_name_to_dir_name (old);
793         new_dir = maildir_full_name_to_dir_name (new);
794
795         /* Chain up to parent's rename_folder_sync() method. */
796         store_class = CAMEL_STORE_CLASS (camel_maildir_store_parent_class);
797         ret = store_class->rename_folder_sync (
798                 store, old_dir, new_dir, cancellable, error);
799
800         if (subfolders) {
801                 if (ret)
802                         ret = rename_traverse_fi (store, store_class, subfolders->child, old, new, cancellable, error);
803
804                 camel_store_free_folder_info (store, subfolders);
805         }
806
807         g_free (old_dir);
808         g_free (new_dir);
809
810         return ret;
811 }
812
813 static void
814 camel_maildir_store_class_init (CamelMaildirStoreClass *class)
815 {
816         CamelStoreClass *store_class;
817         CamelLocalStoreClass *local_class;
818
819         store_class = CAMEL_STORE_CLASS (class);
820         store_class->hash_folder_name = maildir_store_hash_folder_name;
821         store_class->compare_folder_name = maildir_store_compare_folder_name;
822         store_class->create_folder_sync = maildir_store_create_folder_sync;
823         store_class->free_folder_info = camel_store_free_folder_info_full;
824         store_class->get_folder_sync = maildir_store_get_folder_sync;
825         store_class->get_folder_info_sync = maildir_store_get_folder_info_sync;
826         store_class->get_inbox_folder_sync = maildir_store_get_inbox_sync;
827         store_class->delete_folder_sync = maildir_store_delete_folder_sync;
828         store_class->rename_folder_sync = maildir_store_rename_folder_sync;
829
830         local_class = CAMEL_LOCAL_STORE_CLASS (class);
831         local_class->get_full_path = maildir_get_full_path;
832         local_class->get_meta_path = maildir_get_meta_path;
833 }
834
835 static void
836 camel_maildir_store_init (CamelMaildirStore *maildir_store)
837 {
838 }
839
840 static gchar *
841 maildir_get_full_path (CamelLocalStore *ls,
842                        const gchar *full_name)
843 {
844         CamelLocalSettings *local_settings;
845         CamelSettings *settings;
846         CamelService *service;
847         gchar *filename;
848         gchar *dir_name;
849         gchar *path;
850
851         service = CAMEL_SERVICE (ls);
852         settings = camel_service_get_settings (service);
853
854         local_settings = CAMEL_LOCAL_SETTINGS (settings);
855         path = camel_local_settings_dup_path (local_settings);
856
857         dir_name = maildir_full_name_to_dir_name (full_name);
858         filename = g_build_filename (path, dir_name, NULL);
859         g_free (dir_name);
860
861         g_free (path);
862
863         return filename;
864 }
865
866 static gchar *
867 maildir_get_meta_path (CamelLocalStore *ls,
868                        const gchar *full_name,
869                        const gchar *ext)
870 {
871         CamelLocalSettings *local_settings;
872         CamelSettings *settings;
873         CamelService *service;
874         gchar *filename;
875         gchar *dir_name;
876         gchar *path;
877         gchar *tmp;
878
879         service = CAMEL_SERVICE (ls);
880         settings = camel_service_get_settings (service);
881
882         local_settings = CAMEL_LOCAL_SETTINGS (settings);
883         path = camel_local_settings_dup_path (local_settings);
884
885         dir_name = maildir_full_name_to_dir_name (full_name);
886         tmp = g_build_filename (path, dir_name, NULL);
887         filename = g_strconcat (tmp, ext, NULL);
888         g_free (tmp);
889         g_free (dir_name);
890
891         g_free (path);
892
893         return filename;
894 }
895
896 /* Migration from old to maildir++ hierarchy */
897
898 struct _scan_node {
899         CamelFolderInfo *fi;
900
901         dev_t dnode;
902         ino_t inode;
903 };
904
905 static guint scan_hash (gconstpointer d)
906 {
907         const struct _scan_node *v = d;
908
909         return v->inode ^ v->dnode;
910 }
911
912 static gboolean scan_equal (gconstpointer a, gconstpointer b)
913 {
914         const struct _scan_node *v1 = a, *v2 = b;
915
916         return v1->inode == v2->inode && v1->dnode == v2->dnode;
917 }
918
919 static void scan_free (gpointer k, gpointer v, gpointer d)
920 {
921         g_free (k);
922 }
923
924 static gint
925 scan_old_dir_info (CamelStore *store,
926                    CamelFolderInfo *topfi,
927                    GError **error)
928 {
929         CamelLocalSettings *local_settings;
930         CamelSettings *settings;
931         CamelService *service;
932         GQueue queue = G_QUEUE_INIT;
933         struct _scan_node *sn;
934         gchar *path;
935         gchar *tmp;
936         GHashTable *visited;
937         struct stat st;
938         gint res = -1;
939
940         service = CAMEL_SERVICE (store);
941         settings = camel_service_get_settings (service);
942
943         local_settings = CAMEL_LOCAL_SETTINGS (settings);
944         path = camel_local_settings_dup_path (local_settings);
945
946         visited = g_hash_table_new (scan_hash, scan_equal);
947
948         sn = g_malloc0 (sizeof (*sn));
949         sn->fi = topfi;
950         g_queue_push_tail (&queue, sn);
951         g_hash_table_insert (visited, sn, sn);
952
953         while (!g_queue_is_empty (&queue)) {
954                 gchar *name;
955                 DIR *dir;
956                 struct dirent *d;
957                 CamelFolderInfo *last;
958
959                 sn = g_queue_pop_head (&queue);
960
961                 last = (CamelFolderInfo *) &sn->fi->child;
962
963                 if (!strcmp(sn->fi->full_name, "."))
964                         name = g_strdup (path);
965                 else
966                         name = g_build_filename (path, sn->fi->full_name, NULL);
967
968                 dir = opendir (name);
969                 if (dir == NULL) {
970                         g_free (name);
971                         g_set_error (
972                                 error, G_IO_ERROR,
973                                 g_io_error_from_errno (errno),
974                                 _("Could not scan folder '%s': %s"),
975                                 path, g_strerror (errno));
976                         goto exit;
977                 }
978
979                 while ((d = readdir (dir))) {
980                         if (strcmp(d->d_name, "tmp") == 0
981                             || strcmp(d->d_name, "cur") == 0
982                             || strcmp(d->d_name, "new") == 0
983                             || strcmp(d->d_name, ".#evolution") == 0
984                             || strcmp(d->d_name, ".") == 0
985                             || strcmp(d->d_name, "..") == 0)
986                                 continue;
987
988                         tmp = g_build_filename (name, d->d_name, NULL);
989                         if (stat (tmp, &st) == 0 && S_ISDIR (st.st_mode)) {
990                                 struct _scan_node in;
991
992                                 in.dnode = st.st_dev;
993                                 in.inode = st.st_ino;
994
995                                 /* see if we've visited already */
996                                 if (g_hash_table_lookup (visited, &in) == NULL) {
997                                         struct _scan_node *snew = g_malloc (sizeof (*snew));
998                                         gchar *full;
999                                         CamelFolderInfo *fi = NULL;
1000
1001                                         snew->dnode = in.dnode;
1002                                         snew->inode = in.inode;
1003
1004                                         if (!strcmp(sn->fi->full_name, "."))
1005                                                 full = g_strdup (d->d_name);
1006                                         else
1007                                                 full = g_strdup_printf("%s/%s", sn->fi->full_name, d->d_name);
1008
1009                                         fi = camel_folder_info_new ();
1010                                         fi->full_name = full;
1011                                         fi->display_name = g_strdup (d->d_name);
1012                                         snew->fi = fi;
1013
1014                                         last->next =  snew->fi;
1015                                         last = snew->fi;
1016                                         snew->fi->parent = sn->fi;
1017
1018                                         g_hash_table_insert (visited, snew, snew);
1019                                         g_queue_push_tail (&queue, snew);
1020                                 }
1021                         }
1022                         g_free (tmp);
1023                 }
1024                 closedir (dir);
1025                 g_free (name);
1026         }
1027
1028         res = 0;
1029
1030 exit:
1031         g_hash_table_foreach (visited, scan_free, NULL);
1032         g_hash_table_destroy (visited);
1033
1034         g_free (path);
1035
1036         return res;
1037 }
1038
1039 static void
1040 maildir_rename_old_folder (CamelMaildirStore *mstore,
1041                            CamelFolderInfo *fi,
1042                            GCancellable *cancellable,
1043                            GError **error)
1044 {
1045         gchar *new_name = NULL, *old_name;
1046         CamelStoreClass *store_class;
1047
1048         old_name = g_strdup (fi->full_name);
1049         g_strdelimit (old_name, ".", '_');
1050         new_name = maildir_full_name_to_dir_name (old_name);
1051
1052         store_class = CAMEL_STORE_CLASS (camel_maildir_store_parent_class);
1053         store_class->rename_folder_sync (
1054                 (CamelStore *) mstore, fi->full_name, new_name, cancellable, error);
1055
1056         g_free (old_name);
1057         g_free (new_name);
1058 }
1059
1060 static void
1061 traverse_rename_folder_info (CamelMaildirStore *mstore,
1062                              CamelFolderInfo *fi,
1063                              GCancellable *cancellable,
1064                              GError **error)
1065 {
1066         if (fi != NULL) {
1067                 if (fi->child)
1068                         traverse_rename_folder_info (mstore, fi->child, cancellable, error);
1069
1070                 if (strcmp (fi->full_name, ".") && ((!g_str_has_prefix (fi->full_name, ".") && (!fi->parent || !strcmp(fi->parent->full_name, "."))) || 
1071                                         (fi->parent && strcmp (fi->parent->full_name, "."))))
1072                         maildir_rename_old_folder (mstore, fi, cancellable, error);
1073
1074                 traverse_rename_folder_info (mstore, fi->next, cancellable, error);
1075         }
1076 }
1077
1078 static void
1079 maildir_migrate_hierarchy (CamelMaildirStore *mstore,
1080                            GCancellable *cancellable,
1081                            GError **error)
1082 {
1083         CamelFolderInfo *topfi;
1084         gchar *meta_path;
1085
1086         topfi = camel_folder_info_new ();
1087         topfi->full_name = g_strdup (".");
1088         topfi->display_name = g_strdup ("Inbox");
1089
1090         if (scan_old_dir_info ((CamelStore *) mstore, topfi, error) == -1) {
1091                 g_warning ("Failed to scan the old folder info \n");
1092                 camel_folder_info_free (topfi);
1093                 return;
1094         }
1095
1096         traverse_rename_folder_info (mstore, topfi, cancellable, error);
1097
1098         meta_path = maildir_get_meta_path ((CamelLocalStore *) mstore, ".", "maildir++");
1099         g_file_set_contents (meta_path, "maildir++", -1, NULL);
1100
1101         camel_folder_info_free (topfi);
1102         g_free (meta_path);
1103 }