1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Authors: Michael Zucchi <notzed@ximian.com>
5 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
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>
33 #include <glib/gi18n-lib.h>
34 #include <glib/gstdio.h>
36 #include "camel-mbox-folder.h"
37 #include "camel-mbox-store.h"
41 G_DEFINE_TYPE (CamelMboxStore, camel_mbox_store, CAMEL_TYPE_LOCAL_STORE)
43 static const gchar *extensions[] = {
44 ".msf", ".ev-summary", ".ev-summary-meta", ".ibex.index", ".ibex.index.data", ".cmeta", ".lock", ".db", ".journal"
47 /* used to find out where we've visited already */
54 inode_hash (gconstpointer d)
56 const struct _inode *v = d;
58 return v->inode ^ v->dnode;
62 inode_equal (gconstpointer a,
65 const struct _inode *v1 = a, *v2 = b;
67 return v1->inode == v2->inode && v1->dnode == v2->dnode;
71 inode_free (gpointer k,
79 ignore_file (const gchar *filename,
84 /* TODO: Should probably just be 1 regex */
85 flen = strlen (filename);
86 if (flen > 0 && filename[flen - 1] == '~')
89 for (i = 0; i < G_N_ELEMENTS (extensions); i++) {
90 len = strlen (extensions[i]);
91 if (len < flen && !strcmp (filename + flen - len, extensions[i]))
95 if (sbd && flen > 4 && !strcmp(filename + flen - 4, ".sbd"))
101 /* NB: duplicated in maildir store */
103 fill_fi (CamelStore *store,
111 folder = camel_object_bag_peek (store->folders, fi->full_name);
113 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
114 camel_folder_refresh_info_sync (folder, NULL, NULL);
115 fi->unread = camel_folder_get_unread_message_count (folder);
116 fi->total = camel_folder_get_message_count (folder);
117 g_object_unref (folder);
119 CamelLocalStore *local_store;
121 CamelMboxSummary *mbs;
123 local_store = CAMEL_LOCAL_STORE (store);
125 /* This should be fast enough not to have to test for INFO_FAST */
126 folderpath = camel_local_store_get_full_path (
127 local_store, fi->full_name);
129 mbs = (CamelMboxSummary *) camel_mbox_summary_new (NULL, folderpath, NULL);
130 /* FIXME[disk-summary] track exception */
131 if (camel_folder_summary_header_load_from_db ((CamelFolderSummary *) mbs, store, fi->full_name, NULL)) {
132 fi->unread = camel_folder_summary_get_unread_count ((CamelFolderSummary *) mbs);
133 fi->total = camel_folder_summary_get_saved_count ((CamelFolderSummary *) mbs);
136 g_object_unref (mbs);
140 if (camel_local_store_is_main_store (CAMEL_LOCAL_STORE (store)) && fi->full_name
141 && (fi->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_NORMAL)
142 fi->flags = (fi->flags & ~CAMEL_FOLDER_TYPE_MASK)
143 | camel_local_store_get_folder_type_by_full_name (CAMEL_LOCAL_STORE (store), fi->full_name);
146 static CamelFolderInfo *
147 scan_dir (CamelStore *store,
149 CamelFolderInfo *parent,
155 CamelFolderInfo *folders, *tail, *fi;
156 GHashTable *folder_hash;
160 tail = folders = NULL;
162 if (!(dir = g_dir_open (root, 0, NULL)))
165 folder_hash = g_hash_table_new (g_str_hash, g_str_equal);
167 /* FIXME: it would be better if we queue'd up the recursive
168 * scans till the end so that we can limit the number of
169 * directory descriptors open at any given time... */
171 while ((dent = g_dir_read_name (dir))) {
172 gchar *short_name, *full_name, *path, *ext;
178 if (ignore_file (dent, FALSE))
181 path = g_strdup_printf("%s/%s", root, dent);
182 if (g_stat (path, &st) == -1) {
187 if (S_ISDIR (st.st_mode)) {
188 struct _inode in = { st.st_dev, st.st_ino };
190 if (g_hash_table_lookup (visited, &in)) {
196 short_name = g_strdup (dent);
197 if ((ext = strrchr(short_name, '.')) && !strcmp(ext, ".sbd"))
201 full_name = g_strdup_printf("%s/%s", name, short_name);
203 full_name = g_strdup (short_name);
205 if ((fi = g_hash_table_lookup (folder_hash, short_name)) != NULL) {
209 if (S_ISDIR (st.st_mode)) {
210 fi->flags =(fi->flags & ~CAMEL_FOLDER_NOCHILDREN) | CAMEL_FOLDER_CHILDREN;
212 fi->flags &= ~CAMEL_FOLDER_NOSELECT;
215 fi = camel_folder_info_new ();
218 fi->full_name = full_name;
219 fi->display_name = short_name;
223 if (S_ISDIR (st.st_mode))
224 fi->flags = CAMEL_FOLDER_NOSELECT;
226 fi->flags = CAMEL_FOLDER_NOCHILDREN;
235 g_hash_table_insert (folder_hash, fi->display_name, fi);
238 if (!S_ISDIR (st.st_mode)) {
239 fill_fi (store, fi, flags);
240 } else if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE)) {
241 struct _inode in = { st.st_dev, st.st_ino };
243 if (g_hash_table_lookup (visited, &in) == NULL) {
245 struct _inode *inew = g_new (struct _inode, 1);
248 g_hash_table_insert (visited, inew, inew);
250 if ((fi->child = scan_dir (store, visited, fi, path, fi->full_name, flags, error)))
251 fi->flags |= CAMEL_FOLDER_CHILDREN;
253 fi->flags =(fi->flags & ~CAMEL_FOLDER_CHILDREN) | CAMEL_FOLDER_NOCHILDREN;
262 g_hash_table_destroy (folder_hash);
268 xrename (CamelStore *store,
269 const gchar *old_name,
270 const gchar *new_name,
274 CamelLocalStore *ls = (CamelLocalStore *) store;
275 gchar *oldpath, *newpath;
280 oldpath = camel_local_store_get_meta_path (ls, old_name, ext);
281 newpath = camel_local_store_get_meta_path (ls, new_name, ext);
283 oldpath = camel_local_store_get_full_path (ls, old_name);
284 newpath = camel_local_store_get_full_path (ls, new_name);
287 if (g_stat (oldpath, &st) == -1) {
288 if (missingok && errno == ENOENT) {
294 } else if (S_ISDIR (st.st_mode)) {
295 /* use rename for dirs */
296 if (g_rename (oldpath, newpath) == 0 || g_stat (newpath, &st) == 0) {
301 } else if (link (oldpath, newpath) == 0 /* and link for files */
302 ||(g_stat (newpath, &st) == 0 && st.st_nlink == 2)) {
303 if (unlink (oldpath) == 0) {
312 } else if ((!g_file_test (newpath, G_FILE_TEST_EXISTS) || g_remove (newpath) == 0) &&
313 g_rename (oldpath, newpath) == 0) {
327 mbox_store_get_folder_sync (CamelStore *store,
328 const gchar *folder_name,
329 CamelStoreGetFolderFlags flags,
330 GCancellable *cancellable,
333 CamelStoreClass *store_class;
334 CamelLocalStore *local_store;
338 /* Chain up to parent's get_folder_sync() method. */
339 store_class = CAMEL_STORE_CLASS (camel_mbox_store_parent_class);
340 if (!store_class->get_folder_sync (store, folder_name, flags, cancellable, error))
343 local_store = CAMEL_LOCAL_STORE (store);
344 name = camel_local_store_get_full_path (local_store, folder_name);
346 if (g_stat (name, &st) == -1) {
351 if (errno != ENOENT) {
354 g_io_error_from_errno (errno),
355 _("Cannot get folder '%s': %s"),
356 folder_name, g_strerror (errno));
361 if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
363 error, CAMEL_STORE_ERROR,
364 CAMEL_STORE_ERROR_NO_FOLDER,
365 _("Cannot get folder '%s': folder does not exist."),
371 /* sanity check the folder name */
372 basename = g_path_get_basename (folder_name);
374 if (basename[0] == '.' || ignore_file (basename, TRUE)) {
376 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
377 _("Cannot create a folder by this name."));
384 dirname = g_path_get_dirname (name);
385 if (g_mkdir_with_parents (dirname, 0700) == -1 && errno != EEXIST) {
388 g_io_error_from_errno (errno),
389 _("Cannot create folder '%s': %s"),
390 folder_name, g_strerror (errno));
398 fd = g_open (name, O_LARGEFILE | O_WRONLY | O_CREAT | O_APPEND | O_BINARY, 0666);
402 g_io_error_from_errno (errno),
403 _("Cannot create folder '%s': %s"),
404 folder_name, g_strerror (errno));
411 } else if (!S_ISREG (st.st_mode)) {
413 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
414 _("Cannot get folder '%s': not a regular file."),
418 } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
420 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
421 _("Cannot create folder '%s': folder exists."),
428 return camel_mbox_folder_new (store, folder_name, flags, cancellable, error);
431 static CamelFolderInfo *
432 mbox_store_get_folder_info_sync (CamelStore *store,
434 CamelStoreGetFolderInfoFlags flags,
435 GCancellable *cancellable,
438 CamelLocalStore *local_store;
441 struct _inode *inode;
443 gchar *path, *subdir;
451 local_store = CAMEL_LOCAL_STORE (store);
452 path = camel_local_store_get_full_path (local_store, top);
455 /* requesting root dir scan */
456 if (g_stat (path, &st) == -1 || !S_ISDIR (st.st_mode)) {
461 visited = g_hash_table_new (inode_hash, inode_equal);
463 inode = g_malloc0 (sizeof (*inode));
464 inode->dnode = st.st_dev;
465 inode->inode = st.st_ino;
467 g_hash_table_insert (visited, inode, inode);
469 fi = scan_dir (store, visited, NULL, path, NULL, flags, error);
470 g_hash_table_foreach (visited, inode_free, NULL);
471 g_hash_table_destroy (visited);
477 /* requesting scan of specific folder */
478 if (g_stat (path, &st) == -1 || !S_ISREG (st.st_mode)) {
479 char *test_if_subdir = g_strdup_printf("%s.sbd", path);
481 if (g_stat (test_if_subdir, &st) == -1) {
483 g_free (test_if_subdir);
486 g_free (test_if_subdir);
489 visited = g_hash_table_new (inode_hash, inode_equal);
491 basename = g_path_get_basename (top);
493 fi = camel_folder_info_new ();
495 fi->full_name = g_strdup (top);
496 fi->display_name = basename;
500 fill_fi (store, fi, flags);
502 subdir = g_strdup_printf("%s.sbd", path);
503 if (g_stat (subdir, &st) == 0) {
504 if (S_ISDIR (st.st_mode))
505 fi->child = scan_dir (store, visited, fi, subdir, top, flags, error);
509 fi->flags |= CAMEL_FOLDER_CHILDREN;
511 fi->flags |= CAMEL_FOLDER_NOCHILDREN;
515 g_hash_table_foreach (visited, inode_free, NULL);
516 g_hash_table_destroy (visited);
522 static CamelFolderInfo *
523 mbox_store_create_folder_sync (CamelStore *store,
524 const gchar *parent_name,
525 const gchar *folder_name,
526 GCancellable *cancellable,
529 /* FIXME: this is almost an exact copy of CamelLocalStore::create_folder() except that we use
530 * different path schemes... need to find a way to share parent's code? */
531 CamelLocalSettings *local_settings;
532 CamelLocalStore *local_store;
533 CamelFolderInfo *info = NULL;
534 CamelSettings *settings;
535 CamelService *service;
536 gchar *root_path = NULL;
543 service = CAMEL_SERVICE (store);
545 settings = camel_service_ref_settings (service);
547 local_settings = CAMEL_LOCAL_SETTINGS (settings);
548 root_path = camel_local_settings_dup_path (local_settings);
550 g_object_unref (settings);
552 local_store = CAMEL_LOCAL_STORE (store);
554 if (!g_path_is_absolute (root_path)) {
556 error, CAMEL_STORE_ERROR,
557 CAMEL_STORE_ERROR_NO_FOLDER,
558 _("Store root %s is not an absolute path"),
563 if (folder_name[0] == '.' || ignore_file (folder_name, TRUE)) {
565 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
566 _("Cannot create a folder by this name."));
570 if (parent_name && *parent_name)
571 name = g_strdup_printf ("%s/%s", parent_name, folder_name);
573 name = g_strdup (folder_name);
575 path = camel_local_store_get_full_path (local_store, name);
577 dir = g_path_get_dirname (path);
578 if (g_mkdir_with_parents (dir, 0777) == -1 && errno != EEXIST) {
581 g_io_error_from_errno (errno),
582 _("Cannot create directory '%s': %s."),
583 dir, g_strerror (errno));
590 if (g_stat (path, &st) == 0 || errno != ENOENT) {
593 g_io_error_from_errno (errno),
594 _("Cannot create folder: %s: %s"),
595 path, errno ? g_strerror (errno) :
596 _("Folder already exists"));
600 folder = CAMEL_STORE_GET_CLASS (store)->get_folder_sync (
601 store, name, CAMEL_STORE_FOLDER_CREATE, cancellable, error);
603 g_object_unref (folder);
604 info = CAMEL_STORE_GET_CLASS (store)->get_folder_info_sync (
605 store, name, 0, cancellable, error);
617 mbox_store_delete_folder_sync (CamelStore *store,
618 const gchar *folder_name,
619 GCancellable *cancellable,
622 CamelLocalStore *local_store;
628 local_store = CAMEL_LOCAL_STORE (store);
629 name = camel_local_store_get_full_path (local_store, folder_name);
630 path = g_strdup_printf("%s.sbd", name);
632 if (g_rmdir (path) == -1 && errno != ENOENT) {
635 g_io_error_from_errno (errno),
636 _("Could not delete folder '%s':\n%s"),
637 folder_name, g_strerror (errno));
645 if (g_stat (name, &st) == -1) {
648 g_io_error_from_errno (errno),
649 _("Could not delete folder '%s':\n%s"),
650 folder_name, g_strerror (errno));
655 if (!S_ISREG (st.st_mode)) {
657 error, CAMEL_STORE_ERROR,
658 CAMEL_STORE_ERROR_NO_FOLDER,
659 _("'%s' is not a regular file."), name);
664 if (st.st_size != 0) {
666 error, CAMEL_FOLDER_ERROR,
667 CAMEL_FOLDER_ERROR_NON_EMPTY,
668 _("Folder '%s' is not empty. Not deleted."),
674 if (g_unlink (name) == -1 && errno != ENOENT) {
677 g_io_error_from_errno (errno),
678 _("Could not delete folder '%s':\n%s"),
679 name, g_strerror (errno));
684 /* FIXME: we have to do our own meta cleanup here rather than
685 * calling our parent class' delete_folder() method since our
686 * naming convention is different. Need to find a way for
687 * CamelLocalStore to be able to construct the folder & meta
689 path = camel_local_store_get_meta_path (
690 local_store, folder_name, ".ev-summary");
691 if (g_unlink (path) == -1 && errno != ENOENT) {
694 g_io_error_from_errno (errno),
695 _("Could not delete folder summary file '%s': %s"),
696 path, g_strerror (errno));
704 path = camel_local_store_get_meta_path (
705 local_store, folder_name, ".ev-summary-meta");
706 if (g_unlink (path) == -1 && errno != ENOENT) {
709 g_io_error_from_errno (errno),
710 _("Could not delete folder summary file '%s': %s"),
711 path, g_strerror (errno));
719 path = camel_local_store_get_meta_path (
720 local_store, folder_name, ".ibex");
721 if (camel_text_index_remove (path) == -1 && errno != ENOENT) {
724 g_io_error_from_errno (errno),
725 _("Could not delete folder index file '%s': %s"),
726 path, g_strerror (errno));
735 if ((lf = camel_store_get_folder_sync (store, folder_name, 0, cancellable, NULL))) {
736 CamelObject *object = CAMEL_OBJECT (lf);
737 const gchar *state_filename;
739 state_filename = camel_object_get_state_filename (object);
740 path = g_strdup (state_filename);
742 camel_object_set_state_filename (object, NULL);
748 path = camel_local_store_get_meta_path (
749 local_store, folder_name, ".cmeta");
751 if (g_unlink (path) == -1 && errno != ENOENT) {
754 g_io_error_from_errno (errno),
755 _("Could not delete folder meta file '%s': %s"),
756 path, g_strerror (errno));
766 fi = camel_folder_info_new ();
767 fi->full_name = g_strdup (folder_name);
768 fi->display_name = g_path_get_basename (folder_name);
771 camel_store_folder_deleted (store, fi);
772 camel_folder_info_free (fi);
778 mbox_store_rename_folder_sync (CamelStore *store,
781 GCancellable *cancellable,
784 CamelLocalStore *local_store;
785 CamelLocalFolder *folder = NULL;
786 gchar *oldibex, *newibex, *newdir;
789 if (new[0] == '.' || ignore_file (new, TRUE)) {
791 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
792 _("The new folder name is illegal."));
796 /* try to rollback failures, has obvious races */
798 local_store = CAMEL_LOCAL_STORE (store);
799 oldibex = camel_local_store_get_meta_path (local_store, old, ".ibex");
800 newibex = camel_local_store_get_meta_path (local_store, new, ".ibex");
802 newdir = g_path_get_dirname (newibex);
803 if (g_mkdir_with_parents (newdir, 0700) == -1) {
804 if (errno != EEXIST) {
807 g_io_error_from_errno (errno),
808 _("Could not rename '%s': '%s': %s"),
809 old, new, g_strerror (errno));
821 folder = camel_object_bag_get (store->folders, old);
822 if (folder && folder->index) {
823 if (camel_index_rename (folder->index, newibex) == -1 && errno != ENOENT) {
828 /* TODO: camel_text_index_rename should find out if we have an active index itself? */
829 if (camel_text_index_rename (oldibex, newibex) == -1 && errno != ENOENT) {
835 if (xrename(store, old, new, ".ev-summary", TRUE) == -1) {
840 if (xrename(store, old, new, ".ev-summary-meta", TRUE) == -1) {
845 if (xrename(store, old, new, ".cmeta", TRUE) == -1) {
850 if (xrename(store, old, new, ".sbd", TRUE) == -1) {
855 if (xrename (store, old, new, NULL, FALSE) == -1) {
864 g_object_unref (folder);
869 xrename(store, new, old, ".sbd", TRUE);
871 xrename(store, new, old, ".cmeta", TRUE);
873 xrename(store, new, old, ".ev-summary", TRUE);
874 xrename(store, new, old, ".ev-summary-meta", TRUE);
878 camel_index_rename (folder->index, oldibex);
880 camel_text_index_rename (newibex, oldibex);
883 /* newdir is only non-NULL if we needed to mkdir */
890 g_io_error_from_errno (errnosav),
891 _("Could not rename '%s' to %s: %s"),
892 old, new, g_strerror (errnosav));
898 g_object_unref (folder);
904 mbox_store_get_full_path (CamelLocalStore *ls,
905 const gchar *full_name)
907 CamelLocalSettings *local_settings;
908 CamelSettings *settings;
909 CamelService *service;
914 service = CAMEL_SERVICE (ls);
916 settings = camel_service_ref_settings (service);
918 local_settings = CAMEL_LOCAL_SETTINGS (settings);
919 root_path = camel_local_settings_dup_path (local_settings);
921 g_object_unref (settings);
923 g_return_val_if_fail (root_path != NULL, NULL);
925 full_path = g_string_new (root_path);
927 /* Root path may or may not have a trailing separator. */
928 if (!g_str_has_suffix (root_path, G_DIR_SEPARATOR_S))
929 g_string_append_c (full_path, G_DIR_SEPARATOR);
932 while (*cp != '\0') {
933 if (G_IS_DIR_SEPARATOR (*cp)) {
934 g_string_append (full_path, ".sbd");
935 g_string_append_c (full_path, *cp++);
937 /* Skip extranaeous separators. */
938 while (G_IS_DIR_SEPARATOR (*cp))
941 g_string_append_c (full_path, *cp++);
947 return g_string_free (full_path, FALSE);
951 mbox_store_get_meta_path (CamelLocalStore *ls,
952 const gchar *full_name,
955 /*#define USE_HIDDEN_META_FILES*/
956 #ifdef USE_HIDDEN_META_FILES
959 name = g_alloca (strlen (full_name) + strlen (ext) + 2);
960 if ((slash = strrchr (full_name, '/')))
961 sprintf (name, "%.*s.%s%s", slash - full_name + 1, full_name, slash + 1, ext);
963 sprintf (name, ".%s%s", full_name, ext);
965 return mbox_store_get_full_path (ls, name);
967 gchar *full_path, *path;
969 full_path = mbox_store_get_full_path (ls, full_name);
970 path = g_strdup_printf ("%s%s", full_path, ext);
978 camel_mbox_store_class_init (CamelMboxStoreClass *class)
980 CamelStoreClass *store_class;
981 CamelLocalStoreClass *local_store_class;
983 store_class = CAMEL_STORE_CLASS (class);
984 store_class->free_folder_info = camel_store_free_folder_info_full;
985 store_class->get_folder_sync = mbox_store_get_folder_sync;
986 store_class->get_folder_info_sync = mbox_store_get_folder_info_sync;
987 store_class->create_folder_sync = mbox_store_create_folder_sync;
988 store_class->delete_folder_sync = mbox_store_delete_folder_sync;
989 store_class->rename_folder_sync = mbox_store_rename_folder_sync;
991 local_store_class = CAMEL_LOCAL_STORE_CLASS (class);
992 local_store_class->get_full_path = mbox_store_get_full_path;
993 local_store_class->get_meta_path = mbox_store_get_meta_path;
997 camel_mbox_store_init (CamelMboxStore *mbox_store)