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