1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
5 * Authors: Michael Zucchi <notzed@ximian.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
32 #include <glib/gstdio.h>
33 #include <glib/gi18n-lib.h>
35 #include "camel-mh-folder.h"
36 #include "camel-mh-settings.h"
37 #include "camel-mh-store.h"
38 #include "camel-mh-summary.h"
42 G_DEFINE_TYPE (CamelMhStore, camel_mh_store, CAMEL_TYPE_LOCAL_STORE)
51 /* update the .folders file if it exists, or create it if it doesn't */
53 folders_update (const gchar *root,
57 GCancellable *cancellable)
59 gchar *tmp, *tmpnew, *line = NULL;
60 CamelStream *stream, *in = NULL, *out = NULL;
61 gchar *folder_newline;
62 gint flen = strlen (folder);
64 folder_newline = g_strdup_printf ("%s\n", folder);
66 tmpnew = g_alloca (strlen (root) + 16);
67 sprintf (tmpnew, "%s" G_DIR_SEPARATOR_S ".folders~", root);
69 out = camel_stream_fs_new_with_name (
70 tmpnew, O_WRONLY | O_CREAT | O_TRUNC, 0666, NULL);
74 tmp = g_alloca (strlen (root) + 16);
75 sprintf (tmp, "%s" G_DIR_SEPARATOR_S ".folders", root);
76 stream = camel_stream_fs_new_with_name (tmp, O_RDONLY, 0, NULL);
78 in = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_READ);
79 g_object_unref (stream);
81 if (in == NULL || stream == NULL) {
82 if (mode == UPDATE_ADD) {
85 ret = camel_stream_write_string (
86 out, folder_newline, cancellable, NULL);
94 while ((line = camel_stream_buffer_read_line ((CamelStreamBuffer *) in, cancellable, NULL))) {
99 if (strcmp (line, folder) == 0)
103 if (strncmp (line, folder, flen) == 0
104 && (line[flen] == 0 || line[flen] == '/')) {
105 if (camel_stream_write (out, new, strlen (new), cancellable, NULL) == -1
106 || camel_stream_write (out, line + flen, strlen (line) - flen, cancellable, NULL) == -1
107 || camel_stream_write(out, "\n", 1, cancellable, NULL) == -1)
113 gint cmp = strcmp (line, folder);
118 /* found insertion point */
119 ret = camel_stream_write_string (
120 out, folder_newline, cancellable, NULL);
125 } else if (tmp == NULL) {
138 string = g_strdup_printf ("%s\n", line);
139 ret = camel_stream_write_string (
140 out, string, cancellable, NULL);
152 if (mode == UPDATE_ADD) {
155 ret = camel_stream_write_string (
156 out, folder_newline, cancellable, NULL);
162 if (camel_stream_close (out, cancellable, NULL) == -1)
166 /* should we care if this fails? I suppose so ... */
167 g_rename (tmpnew, tmp);
169 unlink (tmpnew); /* remove it if its there */
174 g_object_unref (out);
176 g_free (folder_newline);
180 fill_fi (CamelStore *store,
183 GCancellable *cancellable)
185 CamelLocalStore *local_store;
188 local_store = CAMEL_LOCAL_STORE (store);
189 folder = camel_object_bag_peek (store->folders, fi->full_name);
192 && (flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
193 folder = camel_store_get_folder_sync (
194 store, fi->full_name, 0, cancellable, NULL);
196 if (folder != NULL) {
197 if ((flags & CAMEL_STORE_FOLDER_INFO_FAST) == 0)
198 camel_folder_refresh_info_sync (folder, cancellable, NULL);
199 fi->unread = camel_folder_get_unread_message_count (folder);
200 fi->total = camel_folder_get_message_count (folder);
201 g_object_unref (folder);
204 CamelLocalSettings *local_settings;
205 CamelSettings *settings;
206 CamelService *service;
207 CamelFolderSummary *s;
211 service = CAMEL_SERVICE (store);
213 settings = camel_service_ref_settings (service);
215 local_settings = CAMEL_LOCAL_SETTINGS (settings);
216 path = camel_local_settings_dup_path (local_settings);
218 g_object_unref (settings);
220 /* This should be fast enough not to have to test for INFO_FAST */
222 /* We could: if we have no folder, and FAST isn't specified,
223 * perform a full scan of all messages for their status flags.
224 * But its probably not worth it as we need to read the top of
225 * every file, i.e. very very slow */
227 folderpath = g_strdup_printf ("%s/%s", path, fi->full_name);
228 s = (CamelFolderSummary *) camel_mh_summary_new (
229 NULL, folderpath, NULL);
230 if (camel_folder_summary_header_load_from_db (
231 s, store, fi->full_name, NULL)) {
232 fi->unread = camel_folder_summary_get_unread_count (s);
233 fi->total = camel_folder_summary_get_saved_count (s);
241 if (camel_local_store_is_main_store (local_store) && fi->full_name
242 && (fi->flags & CAMEL_FOLDER_TYPE_MASK) == CAMEL_FOLDER_TYPE_NORMAL)
244 (fi->flags & ~CAMEL_FOLDER_TYPE_MASK) |
245 camel_local_store_get_folder_type_by_full_name (
246 local_store, fi->full_name);
249 static CamelFolderInfo *
250 folder_info_new (CamelStore *store,
254 GCancellable *cancellable)
256 /* FIXME Need to set fi->flags = CAMEL_FOLDER_NOSELECT
257 * (and possibly others) when appropriate. */
261 base = strrchr (path, '/');
263 /* Build the folder info structure. */
264 fi = camel_folder_info_new ();
265 fi->full_name = g_strdup (path);
266 fi->display_name = g_strdup (base ? base + 1 : path);
267 fill_fi (store, fi, flags, cancellable);
272 /* used to find out where we've visited already */
278 /* Scan path, under root, for directories to add folders for. Both
279 * root and path should have a trailing "/" if they aren't empty. */
281 recursive_scan (CamelStore *store,
282 CamelFolderInfo **fip,
283 CamelFolderInfo *parent,
288 GCancellable *cancellable)
290 gchar *fullpath, *tmp;
295 struct _inode in, *inew;
297 /* Open the specified directory. */
299 fullpath = alloca (strlen (root) + strlen (path) + 2);
300 sprintf (fullpath, "%s/%s", root, path);
302 fullpath = (gchar *) root;
304 if (g_stat (fullpath, &st) == -1 || !S_ISDIR (st.st_mode))
307 in.dnode = st.st_dev;
308 in.inode = st.st_ino;
310 /* see if we've visited already */
311 if (g_hash_table_lookup (visited, &in) != NULL)
314 inew = g_malloc (sizeof (*inew));
316 g_hash_table_insert (visited, inew, inew);
319 fi = folder_info_new (store, root, path, flags, cancellable);
324 if (((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) || parent == NULL)) {
325 /* now check content for possible other directories */
326 dp = opendir (fullpath);
330 /* Look for subdirectories to add and scan. */
331 while ((d = readdir (dp)) != NULL) {
332 /* Skip current and parent directory. */
333 if (strcmp(d->d_name, ".") == 0
334 || strcmp(d->d_name, "..") == 0)
337 /* skip fully-numerical entries (i.e. mh messages) */
338 strtoul (d->d_name, &tmp, 10);
342 /* Otherwise, treat at potential node, and recurse,
343 * a bit more expensive than needed, but tough! */
345 tmp = g_strdup_printf("%s/%s", path, d->d_name);
347 store, &fi->child, fi, visited,
348 root, tmp, flags, cancellable);
352 store, &fi->child, fi, visited,
353 root, d->d_name, flags, cancellable);
361 /* scan a .folders file */
363 folders_scan (CamelStore *store,
366 CamelFolderInfo **fip,
368 GCancellable *cancellable)
371 gchar line[512], *path, *tmp;
372 CamelStream *stream, *in;
378 tmp = g_alloca (strlen (root) + 16);
379 sprintf (tmp, "%s/.folders", root);
380 stream = camel_stream_fs_new_with_name (tmp, 0, O_RDONLY, NULL);
384 in = camel_stream_buffer_new (stream, CAMEL_STREAM_BUFFER_READ);
385 g_object_unref (stream);
389 visited = g_hash_table_new (g_str_hash, g_str_equal);
390 folders = g_ptr_array_new ();
392 while ((len = camel_stream_buffer_gets (
393 (CamelStreamBuffer *) in, line,
394 sizeof (line), cancellable, NULL)) > 0) {
396 /* ignore blank lines */
400 /* Check for invalidly long lines,
401 * we abort everything and fallback. */
402 if (line[len - 1] != '\n') {
405 for (i = 0; i < folders->len; i++)
406 camel_folder_info_free (folders->pdata[i]);
407 g_ptr_array_set_size (folders, 0);
415 gint toplen = strlen (top);
417 /* check is dir or subdir */
418 if (strncmp (top, line, toplen) != 0
419 || (line[toplen] != 0 && line[toplen] != '/'))
422 /* check is not sub-subdir if not recursive */
423 if ((flags & CAMEL_STORE_FOLDER_INFO_RECURSIVE) == 0
424 && (tmp = strrchr (line, '/'))
425 && tmp > line + toplen)
429 if (g_hash_table_lookup (visited, line) != NULL)
432 tmp = g_strdup (line);
433 g_hash_table_insert (visited, tmp, tmp);
435 path = g_strdup_printf("%s/%s", root, line);
436 if (g_stat (path, &st) == 0 && S_ISDIR (st.st_mode)) {
437 fi = folder_info_new (
438 store, root, line, flags, cancellable);
439 g_ptr_array_add (folders, fi);
445 *fip = camel_folder_info_build(folders, top, '/', TRUE);
446 g_ptr_array_free (folders, TRUE);
448 g_hash_table_foreach (visited, (GHFunc) g_free, NULL);
449 g_hash_table_destroy (visited);
454 /* FIXME: move to camel-local, this is shared with maildir code */
456 inode_hash (gconstpointer d)
458 const struct _inode *v = d;
460 return v->inode ^ v->dnode;
464 inode_equal (gconstpointer a,
467 const struct _inode *v1 = a, *v2 = b;
469 return v1->inode == v2->inode && v1->dnode == v2->dnode;
473 inode_free (gpointer k,
481 mh_store_get_folder_sync (CamelStore *store,
482 const gchar *folder_name,
483 CamelStoreGetFolderFlags flags,
484 GCancellable *cancellable,
487 CamelStoreClass *store_class;
488 CamelLocalSettings *local_settings;
489 CamelSettings *settings;
490 CamelService *service;
491 CamelFolder *folder = NULL;
492 gboolean use_dot_folders;
497 /* Chain up to parent's get_folder() method. */
498 store_class = CAMEL_STORE_CLASS (camel_mh_store_parent_class);
499 if (store_class->get_folder_sync (
500 store, folder_name, flags, cancellable, error) == NULL)
503 service = CAMEL_SERVICE (store);
505 settings = camel_service_ref_settings (service);
507 local_settings = CAMEL_LOCAL_SETTINGS (settings);
508 path = camel_local_settings_dup_path (local_settings);
510 use_dot_folders = camel_mh_settings_get_use_dot_folders (
511 CAMEL_MH_SETTINGS (settings));
513 g_object_unref (settings);
515 name = g_build_filename (path, folder_name, NULL);
517 if (g_stat (name, &st) == -1) {
518 if (errno != ENOENT) {
521 g_io_error_from_errno (errno),
522 _("Cannot get folder '%s': %s"),
523 folder_name, g_strerror (errno));
527 if ((flags & CAMEL_STORE_FOLDER_CREATE) == 0) {
529 error, CAMEL_STORE_ERROR,
530 CAMEL_STORE_ERROR_NO_FOLDER,
531 _("Cannot get folder '%s': "
532 "folder does not exist."),
537 if (g_mkdir (name, 0777) != 0) {
540 g_io_error_from_errno (errno),
541 _("Could not create folder '%s': %s"),
542 folder_name, g_strerror (errno));
546 /* add to .folders if we are supposed to */
547 /* FIXME: throw exception on error */
550 path, UPDATE_ADD, folder_name,
553 } else if (!S_ISDIR (st.st_mode)) {
555 error, CAMEL_STORE_ERROR,
556 CAMEL_STORE_ERROR_NO_FOLDER,
557 _("Cannot get folder '%s': not a directory."),
561 } else if (flags & CAMEL_STORE_FOLDER_EXCL) {
563 error, CAMEL_ERROR, CAMEL_ERROR_GENERIC,
564 _("Cannot create folder '%s': folder exists."),
569 folder = camel_mh_folder_new (
570 store, folder_name, flags, cancellable, error);
579 static CamelFolderInfo *
580 mh_store_get_folder_info_sync (CamelStore *store,
582 CamelStoreGetFolderInfoFlags flags,
583 GCancellable *cancellable,
586 CamelLocalSettings *local_settings;
587 CamelService *service;
588 CamelSettings *settings;
589 CamelFolderInfo *fi = NULL;
590 gboolean use_dot_folders;
593 service = CAMEL_SERVICE (store);
595 settings = camel_service_ref_settings (service);
597 local_settings = CAMEL_LOCAL_SETTINGS (settings);
598 path = camel_local_settings_dup_path (local_settings);
600 use_dot_folders = camel_mh_settings_get_use_dot_folders (
601 CAMEL_MH_SETTINGS (settings));
603 g_object_unref (settings);
605 /* use .folders if we are supposed to */
606 if (use_dot_folders) {
608 store, path, top, &fi, flags, cancellable);
612 visited = g_hash_table_new (inode_hash, inode_equal);
618 store, &fi, NULL, visited,
619 path, top, flags, cancellable);
621 /* If we actually scanned from root,
622 * we have a "" root node we dont want. */
623 if (fi != NULL && top[0] == 0) {
624 CamelFolderInfo *rfi;
629 camel_folder_info_free (rfi);
632 g_hash_table_foreach (visited, inode_free, NULL);
633 g_hash_table_destroy (visited);
642 mh_store_get_inbox_sync (CamelStore *store,
643 GCancellable *cancellable,
646 return mh_store_get_folder_sync (
647 store, "inbox", 0, cancellable, error);
651 mh_store_delete_folder_sync (CamelStore *store,
652 const gchar *folder_name,
653 GCancellable *cancellable,
656 CamelStoreClass *store_class;
657 CamelLocalSettings *local_settings;
658 CamelSettings *settings;
659 CamelService *service;
660 gboolean use_dot_folders;
664 service = CAMEL_SERVICE (store);
666 settings = camel_service_ref_settings (service);
668 local_settings = CAMEL_LOCAL_SETTINGS (settings);
669 path = camel_local_settings_dup_path (local_settings);
671 use_dot_folders = camel_mh_settings_get_use_dot_folders (
672 CAMEL_MH_SETTINGS (settings));
674 g_object_unref (settings);
676 /* remove folder directory - will fail if not empty */
677 name = g_build_filename (path, folder_name, NULL);
678 if (rmdir (name) == -1) {
681 g_io_error_from_errno (errno),
682 _("Could not delete folder '%s': %s"),
683 folder_name, g_strerror (errno));
690 /* remove from .folders if we are supposed to */
693 path, UPDATE_REMOVE, folder_name,
698 /* Chain up to parent's delete_folder() method. */
699 store_class = CAMEL_STORE_CLASS (camel_mh_store_parent_class);
700 return store_class->delete_folder_sync (
701 store, folder_name, cancellable, error);
705 mh_store_rename_folder_sync (CamelStore *store,
708 GCancellable *cancellable,
711 CamelStoreClass *store_class;
712 CamelLocalSettings *local_settings;
713 CamelSettings *settings;
714 CamelService *service;
715 gboolean use_dot_folders;
719 service = CAMEL_SERVICE (store);
721 settings = camel_service_ref_settings (service);
723 local_settings = CAMEL_LOCAL_SETTINGS (settings);
724 path = camel_local_settings_dup_path (local_settings);
726 use_dot_folders = camel_mh_settings_get_use_dot_folders (
727 CAMEL_MH_SETTINGS (settings));
729 g_object_unref (settings);
731 /* Chain up to parent's rename_folder() method. */
732 store_class = CAMEL_STORE_CLASS (camel_mh_store_parent_class);
733 success = store_class->rename_folder_sync (
734 store, old, new, cancellable, error);
736 if (success && use_dot_folders) {
737 /* yeah this is messy, but so is mh! */
739 path, UPDATE_RENAME, old, new, cancellable);
748 camel_mh_store_class_init (CamelMhStoreClass *class)
750 CamelServiceClass *service_class;
751 CamelStoreClass *store_class;
753 service_class = CAMEL_SERVICE_CLASS (class);
754 service_class->settings_type = CAMEL_TYPE_MH_SETTINGS;
756 store_class = CAMEL_STORE_CLASS (class);
757 store_class->get_folder_sync = mh_store_get_folder_sync;
758 store_class->get_folder_info_sync = mh_store_get_folder_info_sync;
759 store_class->get_inbox_folder_sync = mh_store_get_inbox_sync;
760 store_class->delete_folder_sync = mh_store_delete_folder_sync;
761 store_class->rename_folder_sync = mh_store_rename_folder_sync;
765 camel_mh_store_init (CamelMhStore *mh_store)