Fix FSF address (Tobias Mueller, #470445)
[platform/upstream/evolution-data-server.git] / camel / providers / local / camel-mbox-store.c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2  *
3  * Authors: Michael Zucchi <notzed@ximian.com>
4  *
5  * Copyright(C) 2000 Ximian, Inc.
6  *
7  * This program is free software; you can redistribute it and/or 
8  * modify it under the terms of version 2 of the GNU 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 <errno.h>
27 #include <fcntl.h>
28 #include <string.h>
29 #include <unistd.h>
30 #include <sys/stat.h>
31 #include <sys/types.h>
32
33 #include <glib.h>
34 #include <glib/gi18n-lib.h>
35 #include <glib/gstdio.h>
36
37 #include <libedataserver/e-data-server-util.h>
38
39 #include "camel/camel-exception.h"
40 #include "camel/camel-file-utils.h"
41 #include "camel/camel-private.h"
42 #include "camel/camel-text-index.h"
43 #include "camel/camel-url.h"
44
45 #include "camel-mbox-folder.h"
46 #include "camel-mbox-store.h"
47
48 #define d(x) 
49
50 static CamelLocalStoreClass *parent_class = NULL;
51
52 /* Returns the class for a CamelMboxStore */
53 #define CMBOXS_CLASS(so) CAMEL_MBOX_STORE_CLASS(CAMEL_OBJECT_GET_CLASS(so))
54 #define CF_CLASS(so) CAMEL_FOLDER_CLASS(CAMEL_OBJECT_GET_CLASS(so))
55 #define CMBOXF_CLASS(so) CAMEL_MBOX_FOLDER_CLASS(CAMEL_OBJECT_GET_CLASS(so))
56
57 static CamelFolder *get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex);
58 static void delete_folder(CamelStore *store, const char *folder_name, CamelException *ex);
59 static void rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex);
60 static CamelFolderInfo *create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex);
61 static CamelFolderInfo *get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex);
62 static char *mbox_get_meta_path(CamelLocalStore *ls, const char *full_name, const char *ext);
63 static char *mbox_get_full_path(CamelLocalStore *ls, const char *full_name);
64
65 static void
66 camel_mbox_store_class_init(CamelMboxStoreClass *camel_mbox_store_class)
67 {
68         CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_mbox_store_class);
69
70         parent_class =(CamelLocalStoreClass *)camel_type_get_global_classfuncs(camel_local_store_get_type());
71         
72         /* virtual method overload */
73         camel_store_class->get_folder = get_folder;
74         camel_store_class->delete_folder = delete_folder;
75         camel_store_class->rename_folder = rename_folder;
76         camel_store_class->create_folder = create_folder;
77         
78         camel_store_class->get_folder_info = get_folder_info;
79         camel_store_class->free_folder_info = camel_store_free_folder_info_full;
80
81         ((CamelLocalStoreClass *)camel_store_class)->get_full_path = mbox_get_full_path;
82         ((CamelLocalStoreClass *)camel_store_class)->get_meta_path = mbox_get_meta_path;
83 }
84
85 CamelType
86 camel_mbox_store_get_type(void)
87 {
88         static CamelType camel_mbox_store_type = CAMEL_INVALID_TYPE;
89         
90         if (camel_mbox_store_type == CAMEL_INVALID_TYPE)        {
91                 camel_mbox_store_type = camel_type_register(CAMEL_LOCAL_STORE_TYPE, "CamelMboxStore",
92                                                             sizeof(CamelMboxStore),
93                                                             sizeof(CamelMboxStoreClass),
94                                                             (CamelObjectClassInitFunc) camel_mbox_store_class_init,
95                                                             NULL,
96                                                             NULL,
97                                                             NULL);
98         }
99         
100         return camel_mbox_store_type;
101 }
102
103 static char *extensions[] = {
104         ".msf", ".ev-summary", ".ev-summary-meta", ".ibex.index", ".ibex.index.data", ".cmeta", ".lock"
105 };
106
107 static gboolean
108 ignore_file(const char *filename, gboolean sbd)
109 {
110         int flen, len, i;
111         
112         /* TODO: Should probably just be 1 regex */
113         flen = strlen(filename);
114         if (flen > 0 && filename[flen-1] == '~')
115                 return TRUE;
116
117         for (i = 0; i <(sizeof(extensions) / sizeof(extensions[0])); i++) {
118                 len = strlen(extensions[i]);
119                 if (len < flen && !strcmp(filename + flen - len, extensions[i]))
120                         return TRUE;
121         }
122         
123         if (sbd && flen > 4 && !strcmp(filename + flen - 4, ".sbd"))
124                 return TRUE;
125         
126         return FALSE;
127 }
128
129 static CamelFolder *
130 get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
131 {
132         struct stat st;
133         char *name;
134         
135         if (!((CamelStoreClass *) parent_class)->get_folder(store, folder_name, flags, ex))
136                 return NULL;
137         
138         name = camel_local_store_get_full_path(store, folder_name);
139         
140         if (g_stat(name, &st) == -1) {
141                 char *basename;
142                 char *dirname;
143                 int fd;
144                 
145                 if (errno != ENOENT) {
146                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
147                                              _("Cannot get folder `%s': %s"),
148                                              folder_name, g_strerror (errno));
149                         g_free(name);
150                         return NULL;
151                 }
152                 
153                 if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
154                         camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
155                                              _("Cannot get folder `%s': folder does not exist."),
156                                              folder_name);
157                         g_free(name);
158                         return NULL;
159                 }
160                 
161                 /* sanity check the folder name */
162                 basename = g_path_get_basename (folder_name);
163                 
164                 if (basename[0] == '.' || ignore_file (basename, TRUE)) {
165                         camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
166                                              _("Cannot create a folder by this name."));
167                         g_free (name);
168                         g_free (basename);
169                         return NULL;
170                 }
171                 g_free (basename);
172                 
173                 dirname = g_path_get_dirname(name);
174                 if (g_mkdir_with_parents(dirname, 0777) == -1 && errno != EEXIST) {
175                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
176                                              _("Cannot create folder `%s': %s"),
177                                              folder_name, g_strerror (errno));
178                         g_free(dirname);
179                         g_free(name);
180                         return NULL;
181                 }
182                 
183                 g_free(dirname);
184                 
185                 fd = g_open(name, O_LARGEFILE | O_WRONLY | O_CREAT | O_APPEND | O_BINARY, 0666);
186                 if (fd == -1) {
187                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
188                                              _("Cannot create folder `%s': %s"),
189                                              folder_name, g_strerror (errno));
190                         g_free(name);
191                         return NULL;
192                 }
193                 
194                 g_free(name);
195                 close(fd);
196         } else if (!S_ISREG(st.st_mode)) {
197                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
198                                      _("Cannot get folder `%s': not a regular file."),
199                                      folder_name);
200                 g_free(name);
201                 return NULL;
202         } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
203                 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
204                                       _("Cannot create folder `%s': folder exists."),
205                                       folder_name);
206                 g_free (name);
207                 return NULL;
208         } else
209                 g_free(name);
210         
211         return camel_mbox_folder_new(store, folder_name, flags, ex);
212 }
213
214 static void
215 delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
216 {
217         CamelFolderInfo *fi;
218         CamelException lex;
219         CamelFolder *lf;
220         char *name, *path;
221         struct stat st;
222         
223         name = camel_local_store_get_full_path(store, folder_name);
224         path = g_strdup_printf("%s.sbd", name);
225         
226         if (g_rmdir(path) == -1 && errno != ENOENT) {
227                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
228                                      _("Could not delete folder `%s':\n%s"),
229                                      folder_name, g_strerror(errno));
230                 g_free(path);
231                 g_free(name);
232                 return;
233         }
234         
235         g_free(path);
236         
237         if (g_stat(name, &st) == -1) {
238                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
239                                      _("Could not delete folder `%s':\n%s"),
240                                      folder_name, g_strerror(errno));
241                 g_free(name);
242                 return;
243         }
244         
245         if (!S_ISREG(st.st_mode)) {
246                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
247                                      _("`%s' is not a regular file."), name);
248                 g_free(name);
249                 return;
250         }
251         
252         if (st.st_size != 0) {
253                 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_NON_EMPTY,
254                                      _("Folder `%s' is not empty. Not deleted."),
255                                      folder_name);
256                 g_free(name);
257                 return;
258         }
259         
260         if (g_unlink(name) == -1 && errno != ENOENT) {
261                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
262                                      _("Could not delete folder `%s':\n%s"),
263                                      name, g_strerror(errno));
264                 g_free(name);
265                 return;
266         }
267         
268         /* FIXME: we have to do our own meta cleanup here rather than
269          * calling our parent class' delete_folder() method since our
270          * naming convention is different. Need to find a way for
271          * CamelLocalStore to be able to construct the folder & meta
272          * paths itself */
273         path = camel_local_store_get_meta_path(store, folder_name, ".ev-summary");
274         if (g_unlink(path) == -1 && errno != ENOENT) {
275                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
276                                      _("Could not delete folder summary file `%s': %s"),
277                                      path, g_strerror(errno));
278                 g_free(path);
279                 g_free(name);
280                 return;
281         }
282         
283         g_free(path);
284         
285         path = camel_local_store_get_meta_path(store, folder_name, ".ev-summary-meta");
286         if (g_unlink(path) == -1 && errno != ENOENT) {
287                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
288                                      _("Could not delete folder summary file `%s': %s"),
289                                      path, g_strerror(errno));
290                 g_free(path);
291                 g_free(name);
292                 return;
293         }
294         
295         g_free(path);
296
297         path = camel_local_store_get_meta_path(store, folder_name, ".ibex");
298         if (camel_text_index_remove(path) == -1 && errno != ENOENT) {
299                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
300                                      _("Could not delete folder index file `%s': %s"),
301                                      path, g_strerror(errno));
302                 g_free(path);
303                 g_free(name);
304                 return;
305         }
306         
307         g_free(path);
308
309         path = NULL;
310         camel_exception_init(&lex);
311         if ((lf = camel_store_get_folder(store, folder_name, 0, &lex))) {
312                 camel_object_get(lf, NULL, CAMEL_OBJECT_STATE_FILE, &path, NULL);
313                 camel_object_set(lf, NULL, CAMEL_OBJECT_STATE_FILE, NULL, NULL);
314                 camel_object_unref(lf);
315         } else {
316                 camel_exception_clear(&lex);
317         }
318         
319         if (path == NULL)
320                 path = camel_local_store_get_meta_path(store, folder_name, ".cmeta");
321         
322         if (g_unlink(path) == -1 && errno != ENOENT) {
323                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
324                                      _("Could not delete folder meta file `%s': %s"),
325                                      path, g_strerror(errno));
326                 
327                 g_free(path);
328                 g_free(name);
329                 return;
330         }
331         
332         g_free(path);
333         g_free(name);
334         
335         fi = g_new0(CamelFolderInfo, 1);
336         fi->full_name = g_strdup(folder_name);
337         fi->name = g_path_get_basename(folder_name);
338         fi->uri = g_strdup_printf("mbox:%s#%s",((CamelService *) store)->url->path, folder_name);
339         fi->unread = -1;
340         
341         camel_object_trigger_event(store, "folder_deleted", fi);
342         
343         camel_folder_info_free(fi);
344 }
345
346 static CamelFolderInfo *
347 create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
348 {
349         /* FIXME: this is almost an exact copy of CamelLocalStore::create_folder() except that we use
350          * different path schemes... need to find a way to share parent's code? */
351         const char *toplevel_dir =((CamelLocalStore *) store)->toplevel_dir;
352         CamelFolderInfo *info = NULL;
353         char *path, *name, *dir;
354         CamelFolder *folder;
355         struct stat st;
356         
357         if (!g_path_is_absolute(toplevel_dir)) {
358                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
359                                      _("Store root %s is not an absolute path"), toplevel_dir);
360                 return NULL;
361         }
362         
363         if (folder_name[0] == '.' || ignore_file(folder_name, TRUE)) {
364                 camel_exception_set(ex, CAMEL_EXCEPTION_SYSTEM,
365                                     _("Cannot create a folder by this name."));
366                 return NULL;
367         }
368         
369         if (parent_name && *parent_name)
370                 name = g_strdup_printf("%s/%s", parent_name, folder_name);
371         else
372                 name = g_strdup(folder_name);
373         
374         path = camel_local_store_get_full_path(store, name);
375         
376         dir = g_path_get_dirname(path);
377         if (g_mkdir_with_parents(dir, 0777) == -1 && errno != EEXIST) {
378                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM, _("Cannot create directory `%s': %s."),
379                                      dir, g_strerror(errno));
380                 
381                 g_free(path);
382                 g_free(name);
383                 g_free(dir);
384                 
385                 return NULL;
386         }
387         
388         g_free(dir);
389         
390         if (g_stat(path, &st) == 0 || errno != ENOENT) {
391                 camel_exception_setv(ex, CAMEL_EXCEPTION_STORE_NO_FOLDER,
392                                      _("Cannot create folder: %s: %s"),
393                                      path, errno ? g_strerror(errno) :
394                                      _("Folder already exists"));
395                 
396                 g_free(path);
397                 g_free(name);
398                 
399                 return NULL;
400         }
401         
402         g_free(path);
403         
404         folder =((CamelStoreClass *)((CamelObject *) store)->klass)->get_folder(store, name, CAMEL_STORE_FOLDER_CREATE, ex);
405         if (folder) {
406                 camel_object_unref(folder);
407                 info =((CamelStoreClass *)((CamelObject *) store)->klass)->get_folder_info(store, name, 0, ex);
408         }
409         
410         g_free(name);
411         
412         return info;
413 }
414
415 static int
416 xrename(CamelStore *store, const char *old_name, const char *new_name, const char *ext, gboolean missingok)
417 {
418         CamelLocalStore *ls = (CamelLocalStore *)store;
419         char *oldpath, *newpath;
420         struct stat st;
421         int ret = -1;
422         int err = 0;
423         
424         if (ext != NULL) {
425                 oldpath = camel_local_store_get_meta_path(ls, old_name, ext);
426                 newpath = camel_local_store_get_meta_path(ls, new_name, ext);
427         } else {
428                 oldpath = camel_local_store_get_full_path(ls, old_name);
429                 newpath = camel_local_store_get_full_path(ls, new_name);
430         }
431         
432         if (g_stat(oldpath, &st) == -1) {
433                 if (missingok && errno == ENOENT) {
434                         ret = 0;
435                 } else {
436                         err = errno;
437                         ret = -1;
438                 }
439 #ifndef G_OS_WIN32
440         } else if (S_ISDIR(st.st_mode)) {
441                 /* use rename for dirs */
442                 if (rename(oldpath, newpath) == 0 || stat(newpath, &st) == 0) {
443                         ret = 0;
444                 } else {
445                         err = errno;
446                         ret = -1;
447                 }
448         } else if (link(oldpath, newpath) == 0 /* and link for files */
449                    ||(stat(newpath, &st) == 0 && st.st_nlink == 2)) {
450                 if (unlink(oldpath) == 0) {
451                         ret = 0;
452                 } else {
453                         err = errno;
454                         unlink(newpath);
455                         ret = -1;
456                 }
457         } else {
458                 err = errno;
459                 ret = -1;
460 #else
461         } else if ((!g_file_test (newpath, G_FILE_TEST_EXISTS) || g_remove (newpath) == 0) &&
462                    g_rename(oldpath, newpath) == 0) {
463                 ret = 0;
464         } else {
465                 err = errno;
466                 ret = -1;
467 #endif
468         }
469         
470         g_free(oldpath);
471         g_free(newpath);
472         
473         return ret;
474 }
475
476 static void
477 rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
478 {
479         CamelLocalFolder *folder = NULL;
480         char *oldibex, *newibex, *newdir;
481         int errnosav;
482         
483         if (new[0] == '.' || ignore_file(new, TRUE)) {
484                 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
485                                      _("The new folder name is illegal."));
486                 return;
487         }
488         
489         /* try to rollback failures, has obvious races */
490         
491         oldibex = camel_local_store_get_meta_path(store, old, ".ibex");
492         newibex = camel_local_store_get_meta_path(store, new, ".ibex");
493         
494         newdir = g_path_get_dirname(newibex);
495         if (g_mkdir_with_parents(newdir, 0777) == -1) {
496                 if (errno != EEXIST) {
497                         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
498                                              _("Could not rename `%s': `%s': %s"),
499                                              old, new, g_strerror(errno));
500                         g_free(oldibex);
501                         g_free(newibex);
502                         g_free(newdir);
503                         
504                         return;
505                 }
506                 
507                 g_free(newdir);
508                 newdir = NULL;
509         }
510         
511         folder = camel_object_bag_get(store->folders, old);
512         if (folder && folder->index) {
513                 if (camel_index_rename(folder->index, newibex) == -1 && errno != ENOENT) {
514                         errnosav = errno;
515                         goto ibex_failed;
516                 }
517         } else {
518                 /* TODO: camel_text_index_rename should find out if we have an active index itself? */
519                 if (camel_text_index_rename(oldibex, newibex) == -1 && errno != ENOENT) {
520                         errnosav = errno;
521                         goto ibex_failed;
522                 }
523         }
524         
525         if (xrename(store, old, new, ".ev-summary", TRUE) == -1) {
526                 errnosav = errno;
527                 goto summary_failed;
528         }
529
530         if (xrename(store, old, new, ".ev-summary-meta", TRUE) == -1) {
531                 errnosav = errno;
532                 goto summary_failed;
533         }
534
535         if (xrename(store, old, new, ".cmeta", TRUE) == -1) {
536                 errnosav = errno;
537                 goto cmeta_failed;
538         }
539         
540         if (xrename(store, old, new, ".sbd", TRUE) == -1) {
541                 errnosav = errno;
542                 goto subdir_failed;
543         }
544         
545         if (xrename(store, old, new, NULL, FALSE) == -1) {
546                 errnosav = errno;
547                 goto base_failed;
548         }
549         
550         g_free(oldibex);
551         g_free(newibex);
552         
553         if (folder)
554                 camel_object_unref(folder);
555         
556         return;
557         
558 base_failed:
559         xrename(store, new, old, ".sbd", TRUE);
560 subdir_failed:
561         xrename(store, new, old, ".cmeta", TRUE);
562 cmeta_failed:   
563         xrename(store, new, old, ".ev-summary", TRUE);
564         xrename(store, new, old, ".ev-summary-meta", TRUE);
565 summary_failed:
566         if (folder) {
567                 if (folder->index)
568                         camel_index_rename(folder->index, oldibex);
569         } else
570                 camel_text_index_rename(newibex, oldibex);
571 ibex_failed:
572         if (newdir) {
573                 /* newdir is only non-NULL if we needed to mkdir */
574                 g_rmdir(newdir);
575                 g_free(newdir);
576         }
577         
578         camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
579                              _("Could not rename '%s' to %s: %s"),
580                              old, new, g_strerror(errnosav));
581         
582         g_free(newibex);
583         g_free(oldibex);
584         
585         if (folder)
586                 camel_object_unref(folder);
587 }
588
589 /* used to find out where we've visited already */
590 struct _inode {
591         dev_t dnode;
592         ino_t inode;
593 };
594
595 static guint
596 inode_hash(const void *d)
597 {
598         const struct _inode *v = d;
599         
600         return v->inode ^ v->dnode;
601 }
602
603 static gboolean
604 inode_equal(const void *a, const void *b)
605 {
606         const struct _inode *v1 = a, *v2 = b;
607         
608         return v1->inode == v2->inode && v1->dnode == v2->dnode;
609 }
610
611 static void
612 inode_free(void *k, void *v, void *d)
613 {
614         g_free(k);
615 }
616
617 /* NB: duplicated in maildir store */
618 static void
619 fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
620 {
621         CamelFolder *folder;
622
623         fi->unread = -1;
624         fi->total = -1;
625         folder = camel_object_bag_get(store->folders, fi->full_name);
626         if (folder) {
627                 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
628                         camel_folder_refresh_info(folder, NULL);
629                 fi->unread = camel_folder_get_unread_message_count(folder);
630                 fi->total = camel_folder_get_message_count(folder);
631                 camel_object_unref(folder);
632         } else {
633                 char *path, *folderpath;
634                 CamelMboxSummary *mbs;
635
636                 /* This should be fast enough not to have to test for INFO_FAST */
637                 path = camel_local_store_get_meta_path(store, fi->full_name, ".ev-summary");
638                 folderpath = camel_local_store_get_full_path(store, fi->full_name);
639                 
640                 mbs = (CamelMboxSummary *)camel_mbox_summary_new(NULL, path, folderpath, NULL);
641                 if (camel_folder_summary_header_load((CamelFolderSummary *)mbs) != -1) {
642                         fi->unread = ((CamelFolderSummary *)mbs)->unread_count;
643                         fi->total = ((CamelFolderSummary *)mbs)->saved_count;
644                 }
645
646                 camel_object_unref(mbs);
647                 g_free(folderpath);
648                 g_free(path);
649         }
650 }
651
652 static CamelFolderInfo *
653 scan_dir(CamelStore *store, CamelURL *url, GHashTable *visited, CamelFolderInfo *parent, const char *root,
654          const char *name, guint32 flags, CamelException *ex)
655 {
656         CamelFolderInfo *folders, *tail, *fi;
657         GHashTable *folder_hash;
658         const char *dent;
659         GDir *dir;
660         
661         tail = folders = NULL;
662         
663         if (!(dir = g_dir_open(root, 0, NULL)))
664                 return NULL;
665         
666         folder_hash = g_hash_table_new(g_str_hash, g_str_equal);
667         
668         /* FIXME: it would be better if we queue'd up the recursive
669          * scans till the end so that we can limit the number of
670          * directory descriptors open at any given time... */
671         
672         while ((dent = g_dir_read_name(dir))) {
673                 char *short_name, *full_name, *path, *ext;
674                 struct stat st;
675                 
676                 if (dent[0] == '.')
677                         continue;
678                 
679                 if (ignore_file(dent, FALSE))
680                         continue;
681                 
682                 path = g_strdup_printf("%s/%s", root, dent);
683                 if (g_stat(path, &st) == -1) {
684                         g_free(path);
685                         continue;
686                 }
687 #ifndef G_OS_WIN32              
688                 if (S_ISDIR(st.st_mode)) {
689                         struct _inode in = { st.st_dev, st.st_ino };
690                         
691                         if (g_hash_table_lookup(visited, &in)) {
692                                 g_free(path);
693                                 continue;
694                         }
695                 }
696 #endif          
697                 short_name = g_strdup(dent);
698                 if ((ext = strrchr(short_name, '.')) && !strcmp(ext, ".sbd"))
699                         *ext = '\0';
700                 
701                 if (name != NULL)
702                         full_name = g_strdup_printf("%s/%s", name, short_name);
703                 else
704                         full_name = g_strdup(short_name);
705                                 
706                 if ((fi = g_hash_table_lookup(folder_hash, short_name)) != NULL) {
707                         g_free(short_name);
708                         g_free(full_name);
709                         
710                         if (S_ISDIR(st.st_mode)) {
711                                 fi->flags =(fi->flags & ~CAMEL_FOLDER_NOCHILDREN) | CAMEL_FOLDER_CHILDREN;
712                         } else {
713                                 fi->flags &= ~CAMEL_FOLDER_NOSELECT;
714                         }
715                 } else {
716                         fi = g_new0(CamelFolderInfo, 1);
717                         fi->parent = parent;
718                         
719                         camel_url_set_fragment (url, full_name);
720                         
721                         fi->uri = camel_url_to_string (url, 0);
722                         fi->name = short_name;
723                         fi->full_name = full_name;
724                         fi->unread = -1;
725                         fi->total = -1;
726
727                         if (S_ISDIR(st.st_mode))
728                                 fi->flags = CAMEL_FOLDER_NOSELECT;
729                         else
730                                 fi->flags = CAMEL_FOLDER_NOCHILDREN;
731                         
732                         if (tail == NULL)
733                                 folders = fi;
734                         else
735                                 tail->next = fi;
736                         
737                         tail = fi;
738                         
739                         g_hash_table_insert(folder_hash, fi->name, fi);
740                 }
741                 
742                 if (!S_ISDIR(st.st_mode)) {
743                         fill_fi(store, fi, flags);
744                 } else if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)) {
745                         struct _inode in = { st.st_dev, st.st_ino };
746                         
747                         if (g_hash_table_lookup(visited, &in) == NULL) {
748 #ifndef G_OS_WIN32
749                                 struct _inode *inew = g_new(struct _inode, 1);
750                                 
751                                 *inew = in;
752                                 g_hash_table_insert(visited, inew, inew);
753 #endif
754                                 if ((fi->child = scan_dir (store, url, visited, fi, path, fi->full_name, flags, ex)))
755                                         fi->flags |= CAMEL_FOLDER_CHILDREN;
756                                 else
757                                         fi->flags =(fi->flags & ~CAMEL_FOLDER_CHILDREN) | CAMEL_FOLDER_NOCHILDREN;
758                         }
759                 }
760                 
761                 g_free(path);
762         }
763         
764         g_dir_close(dir);
765         
766         g_hash_table_destroy(folder_hash);
767         
768         return folders;
769 }
770
771 static CamelFolderInfo *
772 get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
773 {
774         GHashTable *visited;
775 #ifndef G_OS_WIN32
776         struct _inode *inode;
777 #endif
778         char *path, *subdir;
779         CamelFolderInfo *fi;
780         char *basename;
781         struct stat st;
782         CamelURL *url;
783         
784         top = top ? top : "";
785         path = camel_local_store_get_full_path(store, top);
786         
787         if (*top == '\0') {
788                 /* requesting root dir scan */
789                 if (g_stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
790                         g_free(path);
791                         return NULL;
792                 }
793                 
794                 visited = g_hash_table_new(inode_hash, inode_equal);
795 #ifndef G_OS_WIN32
796                 inode = g_malloc0(sizeof(*inode));
797                 inode->dnode = st.st_dev;
798                 inode->inode = st.st_ino;
799                 
800                 g_hash_table_insert(visited, inode, inode);
801 #endif
802                 url = camel_url_copy (((CamelService *) store)->url);
803                 fi = scan_dir (store, url, visited, NULL, path, NULL, flags, ex);
804                 g_hash_table_foreach(visited, inode_free, NULL);
805                 g_hash_table_destroy(visited);
806                 camel_url_free (url);
807                 g_free (path);
808                 
809                 return fi;
810         }
811         
812         /* requesting scan of specific folder */
813         if (g_stat(path, &st) == -1 || !S_ISREG(st.st_mode)) {
814                 g_free(path);
815                 return NULL;
816         }
817         
818         visited = g_hash_table_new(inode_hash, inode_equal);
819         
820         basename = g_path_get_basename(top);
821         
822         url = camel_url_copy (((CamelService *) store)->url);
823         camel_url_set_fragment (url, top);
824         
825         fi = g_new0(CamelFolderInfo, 1);
826         fi->parent = NULL;
827         fi->uri = camel_url_to_string (url, 0);
828         fi->name = basename;
829         fi->full_name = g_strdup(top);
830         fi->unread = -1;
831         fi->total = -1;
832         
833         subdir = g_strdup_printf("%s.sbd", path);
834         if (g_stat(subdir, &st) == 0) {
835                 if  (S_ISDIR(st.st_mode))
836                         fi->child = scan_dir (store, url, visited, fi, subdir, top, flags, ex);
837                 else
838                         fill_fi(store, fi, flags);
839         } else
840                 fill_fi(store, fi, flags);
841         
842         camel_url_free (url);
843         
844         if (fi->child)
845                 fi->flags |= CAMEL_FOLDER_CHILDREN;
846         else
847                 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
848         
849         g_free(subdir);
850         
851         g_hash_table_foreach(visited, inode_free, NULL);
852         g_hash_table_destroy(visited);
853         g_free(path);
854         
855         return fi;
856 }
857
858 static char *
859 mbox_get_full_path(CamelLocalStore *ls, const char *full_name)
860 {
861         const char *inptr = full_name;
862         int subdirs = 0;
863         char *path, *p;
864         
865         while (*inptr != '\0') {
866                 if (G_IS_DIR_SEPARATOR (*inptr))
867                         subdirs++;
868                 inptr++;
869         }
870         
871         path = g_malloc (strlen (ls->toplevel_dir) + (inptr - full_name) + (4 * subdirs) + 1);
872         p = g_stpcpy (path, ls->toplevel_dir);
873         
874         inptr = full_name;
875         while (*inptr != '\0') {
876                 while (!G_IS_DIR_SEPARATOR (*inptr) && *inptr != '\0')
877                         *p++ = *inptr++;
878                 
879                 if (G_IS_DIR_SEPARATOR (*inptr)) {
880                         p = g_stpcpy (p, ".sbd/");
881                         inptr++;
882                         
883                         /* strip extranaeous '/'s */
884                         while (G_IS_DIR_SEPARATOR (*inptr))
885                                 inptr++;
886                 }
887         }
888         
889         *p = '\0';
890         
891         return path;
892 }
893
894 static char *
895 mbox_get_meta_path(CamelLocalStore *ls, const char *full_name, const char *ext)
896 {
897 /*#define USE_HIDDEN_META_FILES*/
898 #ifdef USE_HIDDEN_META_FILES
899         char *name, *slash;
900         
901         name = g_alloca (strlen (full_name) + strlen (ext) + 2);
902         if ((slash = strrchr (full_name, '/')))
903                 sprintf (name, "%.*s.%s%s", slash - full_name + 1, full_name, slash + 1, ext);
904         else
905                 sprintf (name, ".%s%s", full_name, ext);
906         
907         return mbox_get_full_path(ls, name);
908 #else
909         char *full_path, *path;
910         
911         full_path = mbox_get_full_path(ls, full_name);
912         path = g_strdup_printf ("%s%s", full_path, ext);
913         g_free (full_path);
914         
915         return path;
916 #endif
917 }