1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Authors: Michael Zucchi <notzed@ximian.com>
5 * Copyright(C) 2000 Ximian, Inc.
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.
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.
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
31 #include <sys/types.h>
34 #include <glib/gi18n-lib.h>
35 #include <glib/gstdio.h>
37 #include <libedataserver/e-data-server-util.h>
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"
45 #include "camel-mbox-folder.h"
46 #include "camel-mbox-store.h"
50 static CamelLocalStoreClass *parent_class = NULL;
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))
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);
66 camel_mbox_store_class_init(CamelMboxStoreClass *camel_mbox_store_class)
68 CamelStoreClass *camel_store_class = CAMEL_STORE_CLASS(camel_mbox_store_class);
70 parent_class =(CamelLocalStoreClass *)camel_type_get_global_classfuncs(camel_local_store_get_type());
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;
78 camel_store_class->get_folder_info = get_folder_info;
79 camel_store_class->free_folder_info = camel_store_free_folder_info_full;
81 ((CamelLocalStoreClass *)camel_store_class)->get_full_path = mbox_get_full_path;
82 ((CamelLocalStoreClass *)camel_store_class)->get_meta_path = mbox_get_meta_path;
86 camel_mbox_store_get_type(void)
88 static CamelType camel_mbox_store_type = CAMEL_INVALID_TYPE;
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,
100 return camel_mbox_store_type;
103 static char *extensions[] = {
104 ".msf", ".ev-summary", ".ev-summary-meta", ".ibex.index", ".ibex.index.data", ".cmeta", ".lock"
108 ignore_file(const char *filename, gboolean sbd)
112 /* TODO: Should probably just be 1 regex */
113 flen = strlen(filename);
114 if (flen > 0 && filename[flen-1] == '~')
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]))
123 if (sbd && flen > 4 && !strcmp(filename + flen - 4, ".sbd"))
130 get_folder(CamelStore *store, const char *folder_name, guint32 flags, CamelException *ex)
135 if (!((CamelStoreClass *) parent_class)->get_folder(store, folder_name, flags, ex))
138 name = camel_local_store_get_full_path(store, folder_name);
140 if (g_stat(name, &st) == -1) {
145 if (errno != ENOENT) {
146 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
147 _("Cannot get folder `%s': %s"),
148 folder_name, g_strerror (errno));
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."),
161 /* sanity check the folder name */
162 basename = g_path_get_basename (folder_name);
164 if (basename[0] == '.' || ignore_file (basename, TRUE)) {
165 camel_exception_set (ex, CAMEL_EXCEPTION_SYSTEM,
166 _("Cannot create a folder by this name."));
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));
185 fd = g_open(name, O_LARGEFILE | O_WRONLY | O_CREAT | O_APPEND | O_BINARY, 0666);
187 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
188 _("Cannot create folder `%s': %s"),
189 folder_name, g_strerror (errno));
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."),
202 } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
203 camel_exception_setv (ex, CAMEL_EXCEPTION_SYSTEM,
204 _("Cannot create folder `%s': folder exists."),
211 return camel_mbox_folder_new(store, folder_name, flags, ex);
215 delete_folder(CamelStore *store, const char *folder_name, CamelException *ex)
223 name = camel_local_store_get_full_path(store, folder_name);
224 path = g_strdup_printf("%s.sbd", name);
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));
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));
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);
252 if (st.st_size != 0) {
253 camel_exception_setv(ex, CAMEL_EXCEPTION_FOLDER_NON_EMPTY,
254 _("Folder `%s' is not empty. Not deleted."),
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));
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
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));
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));
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));
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);
316 camel_exception_clear(&lex);
320 path = camel_local_store_get_meta_path(store, folder_name, ".cmeta");
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));
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);
341 camel_object_trigger_event(store, "folder_deleted", fi);
343 camel_folder_info_free(fi);
346 static CamelFolderInfo *
347 create_folder(CamelStore *store, const char *parent_name, const char *folder_name, CamelException *ex)
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;
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);
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."));
369 if (parent_name && *parent_name)
370 name = g_strdup_printf("%s/%s", parent_name, folder_name);
372 name = g_strdup(folder_name);
374 path = camel_local_store_get_full_path(store, name);
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));
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"));
404 folder =((CamelStoreClass *)((CamelObject *) store)->klass)->get_folder(store, name, CAMEL_STORE_FOLDER_CREATE, ex);
406 camel_object_unref(folder);
407 info =((CamelStoreClass *)((CamelObject *) store)->klass)->get_folder_info(store, name, 0, ex);
416 xrename(CamelStore *store, const char *old_name, const char *new_name, const char *ext, gboolean missingok)
418 CamelLocalStore *ls = (CamelLocalStore *)store;
419 char *oldpath, *newpath;
425 oldpath = camel_local_store_get_meta_path(ls, old_name, ext);
426 newpath = camel_local_store_get_meta_path(ls, new_name, ext);
428 oldpath = camel_local_store_get_full_path(ls, old_name);
429 newpath = camel_local_store_get_full_path(ls, new_name);
432 if (g_stat(oldpath, &st) == -1) {
433 if (missingok && errno == ENOENT) {
440 } else if (S_ISDIR(st.st_mode)) {
441 /* use rename for dirs */
442 if (rename(oldpath, newpath) == 0 || stat(newpath, &st) == 0) {
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) {
461 } else if ((!g_file_test (newpath, G_FILE_TEST_EXISTS) || g_remove (newpath) == 0) &&
462 g_rename(oldpath, newpath) == 0) {
477 rename_folder(CamelStore *store, const char *old, const char *new, CamelException *ex)
479 CamelLocalFolder *folder = NULL;
480 char *oldibex, *newibex, *newdir;
483 if (new[0] == '.' || ignore_file(new, TRUE)) {
484 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
485 _("The new folder name is illegal."));
489 /* try to rollback failures, has obvious races */
491 oldibex = camel_local_store_get_meta_path(store, old, ".ibex");
492 newibex = camel_local_store_get_meta_path(store, new, ".ibex");
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));
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) {
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) {
525 if (xrename(store, old, new, ".ev-summary", TRUE) == -1) {
530 if (xrename(store, old, new, ".ev-summary-meta", TRUE) == -1) {
535 if (xrename(store, old, new, ".cmeta", TRUE) == -1) {
540 if (xrename(store, old, new, ".sbd", TRUE) == -1) {
545 if (xrename(store, old, new, NULL, FALSE) == -1) {
554 camel_object_unref(folder);
559 xrename(store, new, old, ".sbd", TRUE);
561 xrename(store, new, old, ".cmeta", TRUE);
563 xrename(store, new, old, ".ev-summary", TRUE);
564 xrename(store, new, old, ".ev-summary-meta", TRUE);
568 camel_index_rename(folder->index, oldibex);
570 camel_text_index_rename(newibex, oldibex);
573 /* newdir is only non-NULL if we needed to mkdir */
578 camel_exception_setv(ex, CAMEL_EXCEPTION_SYSTEM,
579 _("Could not rename '%s' to %s: %s"),
580 old, new, g_strerror(errnosav));
586 camel_object_unref(folder);
589 /* used to find out where we've visited already */
596 inode_hash(const void *d)
598 const struct _inode *v = d;
600 return v->inode ^ v->dnode;
604 inode_equal(const void *a, const void *b)
606 const struct _inode *v1 = a, *v2 = b;
608 return v1->inode == v2->inode && v1->dnode == v2->dnode;
612 inode_free(void *k, void *v, void *d)
617 /* NB: duplicated in maildir store */
619 fill_fi(CamelStore *store, CamelFolderInfo *fi, guint32 flags)
625 folder = camel_object_bag_get(store->folders, fi->full_name);
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);
633 char *path, *folderpath;
634 CamelMboxSummary *mbs;
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);
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;
646 camel_object_unref(mbs);
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)
656 CamelFolderInfo *folders, *tail, *fi;
657 GHashTable *folder_hash;
661 tail = folders = NULL;
663 if (!(dir = g_dir_open(root, 0, NULL)))
666 folder_hash = g_hash_table_new(g_str_hash, g_str_equal);
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... */
672 while ((dent = g_dir_read_name(dir))) {
673 char *short_name, *full_name, *path, *ext;
679 if (ignore_file(dent, FALSE))
682 path = g_strdup_printf("%s/%s", root, dent);
683 if (g_stat(path, &st) == -1) {
688 if (S_ISDIR(st.st_mode)) {
689 struct _inode in = { st.st_dev, st.st_ino };
691 if (g_hash_table_lookup(visited, &in)) {
697 short_name = g_strdup(dent);
698 if ((ext = strrchr(short_name, '.')) && !strcmp(ext, ".sbd"))
702 full_name = g_strdup_printf("%s/%s", name, short_name);
704 full_name = g_strdup(short_name);
706 if ((fi = g_hash_table_lookup(folder_hash, short_name)) != NULL) {
710 if (S_ISDIR(st.st_mode)) {
711 fi->flags =(fi->flags & ~CAMEL_FOLDER_NOCHILDREN) | CAMEL_FOLDER_CHILDREN;
713 fi->flags &= ~CAMEL_FOLDER_NOSELECT;
716 fi = g_new0(CamelFolderInfo, 1);
719 camel_url_set_fragment (url, full_name);
721 fi->uri = camel_url_to_string (url, 0);
722 fi->name = short_name;
723 fi->full_name = full_name;
727 if (S_ISDIR(st.st_mode))
728 fi->flags = CAMEL_FOLDER_NOSELECT;
730 fi->flags = CAMEL_FOLDER_NOCHILDREN;
739 g_hash_table_insert(folder_hash, fi->name, fi);
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 };
747 if (g_hash_table_lookup(visited, &in) == NULL) {
749 struct _inode *inew = g_new(struct _inode, 1);
752 g_hash_table_insert(visited, inew, inew);
754 if ((fi->child = scan_dir (store, url, visited, fi, path, fi->full_name, flags, ex)))
755 fi->flags |= CAMEL_FOLDER_CHILDREN;
757 fi->flags =(fi->flags & ~CAMEL_FOLDER_CHILDREN) | CAMEL_FOLDER_NOCHILDREN;
766 g_hash_table_destroy(folder_hash);
771 static CamelFolderInfo *
772 get_folder_info(CamelStore *store, const char *top, guint32 flags, CamelException *ex)
776 struct _inode *inode;
784 top = top ? top : "";
785 path = camel_local_store_get_full_path(store, top);
788 /* requesting root dir scan */
789 if (g_stat(path, &st) == -1 || !S_ISDIR(st.st_mode)) {
794 visited = g_hash_table_new(inode_hash, inode_equal);
796 inode = g_malloc0(sizeof(*inode));
797 inode->dnode = st.st_dev;
798 inode->inode = st.st_ino;
800 g_hash_table_insert(visited, inode, inode);
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);
812 /* requesting scan of specific folder */
813 if (g_stat(path, &st) == -1 || !S_ISREG(st.st_mode)) {
818 visited = g_hash_table_new(inode_hash, inode_equal);
820 basename = g_path_get_basename(top);
822 url = camel_url_copy (((CamelService *) store)->url);
823 camel_url_set_fragment (url, top);
825 fi = g_new0(CamelFolderInfo, 1);
827 fi->uri = camel_url_to_string (url, 0);
829 fi->full_name = g_strdup(top);
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);
838 fill_fi(store, fi, flags);
840 fill_fi(store, fi, flags);
842 camel_url_free (url);
845 fi->flags |= CAMEL_FOLDER_CHILDREN;
847 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
851 g_hash_table_foreach(visited, inode_free, NULL);
852 g_hash_table_destroy(visited);
859 mbox_get_full_path(CamelLocalStore *ls, const char *full_name)
861 const char *inptr = full_name;
865 while (*inptr != '\0') {
866 if (G_IS_DIR_SEPARATOR (*inptr))
871 path = g_malloc (strlen (ls->toplevel_dir) + (inptr - full_name) + (4 * subdirs) + 1);
872 p = g_stpcpy (path, ls->toplevel_dir);
875 while (*inptr != '\0') {
876 while (!G_IS_DIR_SEPARATOR (*inptr) && *inptr != '\0')
879 if (G_IS_DIR_SEPARATOR (*inptr)) {
880 p = g_stpcpy (p, ".sbd/");
883 /* strip extranaeous '/'s */
884 while (G_IS_DIR_SEPARATOR (*inptr))
895 mbox_get_meta_path(CamelLocalStore *ls, const char *full_name, const char *ext)
897 /*#define USE_HIDDEN_META_FILES*/
898 #ifdef USE_HIDDEN_META_FILES
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);
905 sprintf (name, ".%s%s", full_name, ext);
907 return mbox_get_full_path(ls, name);
909 char *full_path, *path;
911 full_path = mbox_get_full_path(ls, full_name);
912 path = g_strdup_printf ("%s%s", full_path, ext);